pass11(双写绕过)

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");

$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$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 = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
} ```

我们发现了新代码

1
$file_name = str_ireplace($deny_ext,"", $file_name);
  • 这行代码使用str_ireplace()函数尝试从文件名中移除数组$deny_ext中列出的所有扩展名。

我们可以想到双写来绕过。

当我们把文件后缀设置为**.pphphp**,中间的php会被删除,留下**.php**,这样我们就上传成功了。

空字符

0x00,%00都是空字符,0x00是编程语言中使用的,%00是在url编码中使用的,他们的作用是截断字符串,例如“up0x00load”,系统在读到0x00时自动停止,那么读取到的只剩下“up”。

pass12(%00绕过)

查看源码:

1
2
3
4
5
6
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

我们发现文件的保存路径是可以被修改的,我们可以想到用%00(因为这里的路径是通过get方法获取的)将$img_path = $_GET[‘save_path’]后面的代码给截断掉,这样我们就可以任意修改文件名了。

1740195984936

不过这需要php版本低于5.3

pass13(0x00)

这关是用post方法传输的,我们抓包完发现可以在请求体中修改路径,不过我们这里不能直接在后面加上0x00,因为php并不会把0x00解释为空字符并停止读取,我们可以在后缀名之后加个空格或者随便加个字符,再把他改为16进制为0的字符,这样就会被识别为空字符以截断$img_path = $_GET[‘save_path’]后面的语句。

这也需要低版本php

pass14(字节标识符绕过)

字节标识符(文件头)是指文件最开始的几个字节,用于标识文件类型。

常见的如下:

  1. JPEG 图像文件
    • 字节标识: FF D8 FF
  2. PNG 图像文件
    • 字节标识: 89 50 4E 47 0D 0A 1A 0A
  3. GIF 图像文件
    • 字节标识: 47 49 46 38 37 61 (GIF87a) 或 47 49 46 38 39 61 (GIF89a)
  4. PDF 文件
    • 字节标识: %PDF- (ASCII 字符串,通常后跟版本号)

我们查看本关的源码:

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;
}

这是用来检测图片前俩个字节是否是jpg,png,gif的标识符的代码。

我们利用vscode中的hex editor将木马文件的前两个字节改为89 50

1740213110637

可以看到它已经被识别为png文件,这时候我们需要用到文件包含漏洞来使得这个文件能被解释为php文件。

1740213287533

这里是通过get方法获取文件来进行包含。由于include.php在我们上传的木马的上一级目录,我们需要这样1740213576970

这时include.php文件便可以包含并以php解释我们的木马。

我们再用蚁剑测试可以连接成功。

pass15(图片马)

查看源码:

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;
}
}
  • getimagesize($filename) 函数获取图像文件的相关信息,包括图像的宽度和高度,以及一个表示图像类型的常量(=1,2,3…)。
  • image_type_to_extension() 函数将 getimagesize() 返回的图像类型转换为对应的文件扩展名。

我们修改它的文件头为47 49 46 38即gif,再用文件包含漏洞,便能成功连接。

1740215865791

1740216017771

pass16

和15关差不多,不过检测函数变成了exif_imagetype()它只会检测图片类型,不会检测高宽什么的。

less17(二次渲染)

查看源码发现有新函数

1
$im = imagecreatefromjpeg($target_path);
  • imagecreatefromjpeg():这是PHP中的一个内置函数,用于从JPEG文件创建一个新的图像资源。该函数读取指定的JPEG文件,并返回一个图像标识符,代表了一幅图像,可以用于进一步的图像处理。

当然不止重新生成jpeg的,还有别的,问题就是重新生成后文件中的一些内容会被改写,我们的木马就可能被改写,因此我们要在不会被改写的地方写入我们的一句话木马再用文件包含漏洞进行上传shell。这里我们用最简单的gif二次渲染,别的图片可以查看这个博客【文件上传绕过】——二次渲染漏洞_二次渲染绕过-CSDN博客

1740233716974

这是在未改写区修改后的图片,上传后发现还在

1740233772282

我们再利用文件上传漏洞

1740233904730

成功。

less18(条件竞争)

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;

if(move_uploaded_file($temp_file, $upload_file)){
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);
}
}else{
$msg = '上传出错!';
}
}

原理

发现服务器先通过move_uploaded_file()将文件保存到指定路径再来判断后缀名是否相符。这时文件是会在服务器上先存在一定的时间的,我们这时在访问这个文件(这个文件会生成一个含有一句话木马的文件),只要在文件还存在的时候访问,这样木马便会生成在upload目录下。

我们需要用到bp来进行不断发送与访问文件。且只有一定的几率成功。

less19(apache解析漏洞+条件竞争)

apache解析漏洞

apc是从又往左读取文件名的,如果一个文件shell.php.*,其中的 *是apc不能解析的后缀,那么他就会往前读也就是以php的形式解析。

思路

查看源码可知

1
2
3
4
5
6
7
8
9
10
11
12
$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 );
}

这关的文件移动是在文件检查之前,在文件重命名之后,那么我们可以利用条件竞争在文件被重命名之前访问shell.php.7z,这样就有机会在upload目录下生成木马,不过复现只是有概率成功,主要看服务器处理的速度与请求发送的速度。

less20

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

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 = '禁止保存为该类型文件!';
}

} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

发现我们可以自定义文件名,且只有黑名单过滤,且没有条件竞争。我们可以利用user.ini,点加空格加点,加点,::$DATA,apc解析漏洞,不过这些都要注意php或者apache版本不能太高。

less21

看源码:

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
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 = "请选择要上传的文件!";
}

首先,判断文件的Content-Type类型是否在白名单中

如果POST接收的save_name为空,则赋值为$_FILES['upload_file']['name'],不为空就为本身

if (!is_array($file)) - 这一行检查变量$file是否不是数组。如果$file不是数组,那么花括号内的代码将会被执行。

explode() 函数把字符串根据**”.”**打散为数组.

end()函数将 array的内部指针移动到最后一个单元并返回其值
reset()函数将 array 的内部指针倒回到第一个单元并返回第一个数组单元的值
count() 函数计算数组中的单元数目或对象中的属性个数

重点是 $file_name = reset($file) . '.' . $file[count($file) - 1];

是根据flie数组的第一个和数组元素-1个的元素命名的,那么我们可以让file[0]=shell,file[2]=php,file[3]=jpg,这样3-1=2,file[2]就是我们想要的php后缀。

1740299731189

1740299772434

上传成功。