Web-EZ FileUpload
题目来源:2023MOECTF
题目概述
一道经典的文件上传,看到这个思路马上就来了,就是上传一个php一句话木马,然后用蚁剑回连
初步尝试
我们先抓个包看看,这里有一个小插曲,就是如果我用127.0.0.1这个环回地址来访问,burp是抓不到包的,用局域网中的ip加端口号来访问就可以抓到包,配置好burp和浏览器的代理,先任意上传一个文件,得到如下提示:
抓包并修改文件后缀为png发现能够上传,说明这是一个前端验证,很好绕过
我们再次尝试,这次传一个符合要求的文件:
发现上传成功,并返回了一个地址,但是直接访问这个地址会返回404,根据抓包结果以及题目的hint,猜想正确地址应该只有upload以及后面的url,尝试发现可以访问。
那么,我们是不是只要通过抓包修改后缀绕过前端,就可以上传php木马了呢?
进一步尝试
首先我们先构造一个一句话木马,使用文本编辑器编写,完成后将后缀改为png,下面附上一句话木马的基本知识:
什么是一句话木马
一句话木马就是只需要一行代码的木马,短短一行代码,就能做到和大马相当的功能。为了绕过waf的检测,一句话木马出现了无数中变形,但本质是不变的:木马的函数执行了我们发送的命令。
我们如何发送命令,发送的命令如何执行?
我们可以通过GET 、POST 、COOKIE这三种方式向一个网站提交数据,一句话木马用$_GET[‘ ‘]、$_POST[‘ ‘]、$_COOKIE[‘ ‘] 接收我们传递的数据,并把接收的数据传递给一句话木马中执行命令的函数,进而执行命令。
所以看到的经典一句话木马大多都是只有两个部分,一个是可以执行代码的函数部分,一个是接收数据的部分。
例如:
其中eval就是执行命令的函数,$_POST[‘a’]就是接收的数据。eval函数把接收的数据当作PHP代码来执行。这样我们就能够让插入了一句话木马的网站执行我们传递过去的任意PHP语句。这便是一句话木马的强大之处。
因为木马是接收post请求中 “a” 的数据( $_POST[‘a’]),所以我们必须以post方法发送数据并且将我们要执行的代码赋值给“a”。如果把木马中的post替换成get,那么我么就需要以GET方法发送“a”,( 就像这样: http://127.0.0.1/test.php?a=phpinfo(); )我就不再另行演示了。
使用 其他函数制作一句话木马
assert函数
create_function函数
把用户传递的数据生成一个函数fun(),然后再执行fun()
call_user_func回调函数
call_user_func这个函数可以调用其它函数,被调用的函数是call_user_func的第一个函数,被调用的函数的参数是call_user_func的第二个参数。这样的一个语句也可以完成一句话木马。一些被waf拦截的木马可以配合这个函数绕过waf。
preg_replace函数
这个函数原本是利用正则表达式替换符合条件的字符串,但是这个函数有一个功能——可执行命令。这个函数的第一个参数是正则表达式,按照PHP的格式,表达式在两个“/”之间。如果我们在这个表达式的末尾加上“e”,那么这个函数的第二个参数就会被当作代码执行。
file_put_contents函数
利用函数生成木马
‘;
file_put_contents(“Trojan.php”, $test);
?>
函数功能:生成一个文件,第一个参数是文件名,第二个参数是文件的内容。
如何让一句话木马绕过waf ?
waf是网站的防火墙,例如安全狗就是waf的一种。waf通常以关键字判断是否为一句话木马,但是一句话木马的变形有很多种,waf根本不可能全部拦截。想要绕过waf,需要掌握各种PHP小技巧,掌握的技巧多了,把技巧结合起来,设计出属于自己的一句话木马。
PHP变量函数
第三行使用了变量函数$a,变量储存了函数名eval,便可以直接用变量替代函数名。
PHP可变变量
看这句就能理解上述语句:$$aa = $($aa) = $ (‘bb’) = $bb = “eval”
str_replace函数
函数功能:在第三个参数中,查找第一个参数,并替换成第二个参数。这里第二个参数为空字符串,就相当于删除”Waldo”。
base64_decode函数
这里是base64解密函数,”ZXZhbA==”是eval的base64加密。
“.”操作符
parse_str函数
执行这个函数后,生成一个变量$a,值为字符串”eval”
更多技巧…
多实战,多谷歌!
上述六种技巧每一种单独使用都不能绕过waf,但是与 第三大点提到的函数混合起来使用,就可以顺利的欺骗waf。
tips:使用一句话木马的时候可以在函数前加”@”符,这个符号让php语句不显示错误信息,增加隐蔽性。
再来一个小栗子
这里又有一个技巧,创建函数 fun(),返回post中“a”的数据。我的这个例子很明显是一句话木马,但是安全狗却扫不出,D盾也是如此。
上传写好的一句话,并且抓包修改后缀为php,发现返回错误:
这里的提示和之前的不一样,说明我们绕过了前端,但没有绕过后端的验证
找到突破口
观察包数据,发现上传文件需要post请求一个根目录下的upload.php文件,我们直接访问,发现源代码泄露,直接给出了upload.php的源码,泄露了后端验证的过程,以下给出源码:
<?php
$targetDir = 'uploads/';
$allowedExtensions = ['png'];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
$tmp_path = $_FILES['file']['tmp_name'];
if ($file['type'] !== 'image/png') {
die(json_encode(['success' => false, 'message' => '文件类型不符合要求']));
}
if (filesize($tmp_path) > 512 * 1024) {
die(json_encode(['success' => false, 'message' => '文件太大']));
}
$fileName = $file['name'];
$fileNameParts = explode('.', $fileName);
if (count($fileNameParts) >= 2) {
$secondSegment = $fileNameParts[1];
if ($secondSegment !== 'png') {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
} else {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
$uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);
if (move_uploaded_file($tmp_path, $uploadFilePath)) {
die(json_encode(['success' => true, 'file_path' => $uploadFilePath]));
} else {
die(json_encode(['success' => false, 'message' => '文件上传失败']));
}
}
else{
highlight_file(__FILE__);
}
?>
审计代码发现,程序逻辑是将上传文件的文件名以 “.” 来分割并返回一个列表,然后判断这个列表的第二项是否为png,如果是则通过,如果不是则不通过。那么思路就来了,因为只有最后一个点才会被识别为后缀,但是程序却判断不是列表的最后一项,那么我们只需要构造形如”xxx.png.php”的文件名就可以成功将php木马上传。
得到flag
先上传一个png文件,再通过抓包修改后缀,上传成功并返回地址:
然后用蚁剑回连(蚁剑工具在github可以下载),连接后再根目录找到flag:
踩坑总结
1.一定要注意观察数据包的内容,有时一个小小的请求地址,有可能会泄露重要的代码