upload-labs通关笔记
upload-labs靶场
pass1 禁用js绕过
我们先在桌面创建一个php文件,内容为
然后将该文件上传pass1
我们右键查看源代码
发现存在js代码在限制文件上传类型
那就在浏览器禁用js
我用的是火狐
那就在地址栏输入about:config,点击继续
搜索框搜索java,找到javascript:enble
双击变成false即禁用js
将靶场重新加载一次,再次点击上传就可以上传成功了
我们试着在网页上打开刚刚上传的php文件
问题不大~~
pass2 content-type绕过
我们先查看下源代码
发现这次没有前端js校验,而是content-type判断文件类型
那就尝试用bp抓包,再修改它的信息
上传成功
网页测试下
pass3 黑名单绕过
查看源代码
我们看到源代码对于我们输入的文件类型做了很多过滤(大小写,字符串::$DATA,首尾去空)
但是这是个黑名单限制,限制不能上传的文件类型仅仅是
asp,aspx,php,jsp
那我们还可以使用
phtml,phps,php5,pht
那就改一下文件类型
完成上传
网页测试
pass4 .htaccess绕过
查看下源代码
依旧是黑名单,但是我们可以看到已经过滤了更多的文件类型
但是.htaccess还没有过滤
前提条件(1.mod_rewrite模块开启。2.AllowOverride All)
如果是用的phpstudy集成环境,应该使用2018版本的小皮面板。
亲测phpstudy_pro无法解析
htaccess文件里面的内容写
SetHandler application/x-httpd-php
我们先把这个文件上传到靶场的文件夹里面(注意,此文件就是.htaccess就行)
然后把我们需要上传的php文件修改后缀名,可以是jpg,png,gif等
然后我们在浏览器上测试一下
pass5 .user.ini绕过
我们查看一下源码
可以看到此时 .htaccess已经被黑名单加入,直接过滤
删除了文件名末尾的点,然后也过滤了大小写,::$DATA,首尾去空。
但是php7和ini都没有被过滤
我们查看一下提示
可以看到说上传目录里面已经有php文件了
那我们可以使用.user.ini,因为.user.ini 中的字段会被 php 视为配置文件来处理,从而导致 php 的文件解析漏洞
在phpstudy集成环境中,我们应该使用
有nts的(点击切换版本就可以找到)
nts也就是包含了服务器使用FastCGI模式。
.user.ini 解析漏洞需要三个前提条件:
服务器脚本语言为PHP
服务器使用CGI/FastCGI模式
上传目录下要有可执行的php文件
.user.ini里面的内容我们可以设置为
意思是所有的php文件里面都要包含webshell.jpg文件
我们上传试试
上传成功
我们再上传准备的文件webshell.jpg
最后网页上测试一下(打开readme.php)
pass6 大小写绕过
查看源码
发现没有进行大小写过滤,那就大小写绕过。可以直接修改后缀名
然后我们上传的包在bp上看一下
点击go
发现我们传过去的php被修改了文件名
成功上传
浏览器测试,使用刚刚bp上看到的文件名,成功
pass7 空格绕过
查看源代码
发现没有对其进行首尾去空,那么我们可以使用空格绕过
网页上测试一波
pass8 点绕过
查看源码
我们发现并没有对文件名末尾的点进行过滤,那考虑使用点绕过
浏览器上访问测试
pass9 ::$DATA绕过
查看源代码
发现没有对::$DATA进行过滤
我们知道在windows下如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名.且保持"::$DATA"之前的文件名
老样子,bp抓包再进行修改。::$DATA加在后缀名后面
浏览器测试
pass10 点+空格+点绕过
我们查看一下源码
可以看到此时 .htaccess已经被黑名单加入,直接过滤
删除了文件名末尾的点,然后也过滤了大小写,::$DATA,首尾去空。
我们考虑一手点+空格+点绕过
直接修改文件的拓展名是不可以的,那我们通过bp抓包以后修改
成功上传
浏览器上测试
pass11 双写绕过
我们查看一下源码
我们可以看到,源码使用了str_ireplace()函数来将限制的文件类型给替换为空。但是我们知道,这个函数只会扫描一次。
我们可以尝试使用双写绕过。
将文件的拓展名改成 pphphp或者 phphpp.
然后直接上传
网页上验证
pass12 %00截断绕过
我们先查看一下源码
发现这是白名单限制。然后首先使用了in_array()函数来判断文件拓展名是否属于 gif|png|jpg
,然后又使用了move_uploaded_file()函数来判断文件的合法性。
我们考虑到如果使用%00就可以截断该函数。所以可以先用bp抓包
然后在上传的路径上使用%00,文件拓展名修改为gif|png|jpg
来绕过in_array()函数
将包放掉
我们在浏览器上测试一下
pass13 hex00截断绕过
我们先查看一下源码
源码和上一关大体相同,唯一区别是本关用的是post来传参
那么我们同样可以使用00截断,但是需要在hex中修改来完成
先用bp抓包,然后修改其save_path和filename
然后点击hex来修改,找到php+,将+对应的hex修改成00(2b变成00)
放包
浏览器测试
pass14 unpack图片马
进入14关,我们可以很快看到本关的任务要求
我们首先制作一个图片马
要求:一张图片,一个你准备好的一句话或者webshell代码
这里的图片需要选择尽可能小或者简单的。如果过于“精美”,emm…做好测试的时候失败的准备吧。。
就像这样
如果你还是想用这张图片,也可以将出现错误的那一行(我这里是94行)记住。然后将图片马用记事本之类的打开,把错误的那一行删除,然后再上传修改好的图片马。
制作图片马需要你准备好的图片
和准备好的一句话或者webshell代码
位于同一目录之下。然后用cmd命令行跳转到该目录之下,然后敲命令如
copy xx.jpg/b+xx.php xx.jpg
我们查阅资料可以知道
1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG。
2.Jpg图片文件包括2字节:FF D8。
3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
4.Bmp图片文件包括2字节:42 4D。即为 BM
之前我制作的图片马是jpg格式,上传以后
浏览器的测试
如果需要上传png格式的图片马,直接修改其文件拓展名的话上传以后还是会恢复成原来格式,那么我们这边可以使用winhex,16进制的编辑器来对其进行修改。
png格式则修改为:
用bp抓包上传试试
发现我们上传的确实是png格式,然后在浏览器的测试也正常
gif需要另外找gif格式图片才能上传
pass15 getimagesize()图片马
查看源代码
和14关的做法一样,只是此关使用的是
getimagesize()函数来获取图片的具体信息,进而做出判断。
使用bp抓包要上传的图片马
放包以后
浏览器测试
pass16 exif_imagetype()绕过
此关需要开启php_exif,然后运用了exif_imagetype()来读取一个图像的第一个字节并检查其后缀名。
然后绕过的方式和上一关是一样的。用图片马伪装绕过。
pass17 二次渲染绕过
查看源代码
我们可以看到使用了
pass18 条件竞争
我们查看一下源代码
我们可以看到,依旧是白名单过滤规则。当上传的文件不是.jpg|.png|.gif类型
时,输出
只允许上传.jpg|.png|.gif类型文件!
然后利用unlink()函数直接删除该不符合条件的文件。
由于这里我们使用爆破,通过多次上传此文件并且在上传的时候同时在浏览器访问该文件路径来实现上传webshell。
1、准备一个webshell文件,文件名我这里就叫test.php,内容是
<?php fputs(fopen('shell.php','w'),'<?php phpinfo();?>');?>
当运行该php文件时,将会在同一目录下创建一个叫shell.php的文件,里面的内容是
<?php phpinfo();?>
2、上传这个文件,然后用bp抓上传的包
右键 send to intruder,选择payload
设置成
3、bp直接开始爆破
4、浏览器重复访问
http://localhost/upload-labs-master/upload/test.php
当浏览器访问后呈现空白,说明我们已经运行成功了test.php文件
5、尝试访问shell.php文件,成功
http://localhost/upload-labs-master/upload/shell.php
补充:当然,如果觉得手动重复刷新访问麻烦,可以使用python脚本访问。
import requests
url = "http://localhost/upload-labs-master/upload/test.php"
url1 = "http://localhost/upload-labs-master/upload/shell.php"
while 1:
html = requests.get(url)
html1 = requests.get(url1)
if html1.status_code == 200:
print("successfull to upload!")
break
之所以不直接判断test.php访问的状态码是为了防止有时候访问到test.php文件但是因为某些原因没能正常运行从而没能产生正常的shell.php文件。
pass19
提示我们需要代码审计,于是查看源码
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){
$ret = $this->isUploadedFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// if flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};