平台地址:内部地址不便放出
这是哈工大热身赛的一题 ~~这完全不热身~~
考点
- sprintf函数漏洞
- 变量覆盖
- SSRF读文件
- linux基本知识
- ssti
- Python字符串格式化漏洞
解题
打开靶机给了源码
``` php
<?php
$start = "The content is ";
extract($_GET);
if(isset($url)) {
$u = parse_url($url);
var_dump($u);
if(!in_array($u['scheme'], array('http', 'https'))) {
die('对不起,您使用的协议暂时不支持');
}
if(preg_match('/127|host/', $u['host'])) {
die('对不起,我不喜欢数字');
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
echo sprintf("$start%dn", $result);
curl_close($ch);
} else {
highlight_file(FILE);
}
``
SSRF
简单的审计后发现,明显是一个读取文件的代码,过滤比较严格,只允许使用
http或
https,且IP中不允许含有
127或
host,否则返回错误
SSRF
但是我们既然需要通过读内网文件,就必须包含·127.0.0.1·或者·localhost·,我们如何绕过呢
crul可以解析16进制或者8进制地址
测试后我们可以发现所以我们这里将
127.0.0.1构造成
http://0177.0.0.1/或
http://2130706433/均可以解析成
127.0.0.1之后进行端口扫描,发现5000端口开放
echo sprintf(“$start%dn”, $result);
构造完成后,我们发现该语句的输出限定为
%d。即数字,而变量
$start与
%d连接在一起,我们就可以用
sprintf函数漏洞注释掉
%d并设置成
%s`
传入start=%1$’%s’
或%251$s
均可以完成变量覆盖。现在我们的payload是:?start=%251$s&url=http://0177.0.0.1:5000
回显
我们传入/query?path=
即可获得相应文件信息,尝试读一下etc/passwd
后发现给了回显,那这里就应该是读文件的地方了
发现根目录下的fl4g
,尝试读取后提示 nonono,it is a secret!!!0
看来是不能直接读取的
读一下源码
readfile.py
python
class FileReader:
def __init__(self, file):
self.file = file
def __str__(self):
if 'fl4g' in self.file.path:
return 'nonono,it is a secret!!!'
else:
output = 'The file you read is:n'
filepath = (self.file.dir + '/{file.name}').format(file=self.file)
output += filepath
output += 'nnThe content is:n'
try:
f = open(filepath,'r')
content = f.read()
f.close()
except:
content = 'can't read'
output += content
output += 'nnOther files under the same folder:n'
output += 'n'.join(self.file.listDir())
return output.replace('n','')
file.py
``` python
import os
import os.path
class File:
"The file class"
def init(self, path):
self.path = path
self.name = os.path.basename(self.path)
self.dir = os.path.dirname(self.path)
def listDir(self):
return os.listdir(os.path.dirname(self.dir))
```
main.py
``` python
from flask import Flask, request
from file import File
from readfile import FileReader
app = Flask(name)
@app.route('/query')
def query():
path = request.args.get('path')
if not path:
return '请输入路径'
file = FileReader(File(path))
return str(file)
@app.route('/')
def index():
return 'Use /query?path= to query what you want. And what you want is at /fl4g/flag'
if name == 'main':
app.run(port=8890)
```
源码审计
filereader
类中file=self.file
由于self.file
是一个Filereader
类,所以file.name.file
也就等于Filereader.file
python字符串格式化漏洞,
如果我们传入的整体路径为/fl4{file.name[3]}/flag
由于file.name.file
与self.file.file=flag
相似,所以这里的{file.name[3]}=='g'
可以成功绕过,最终读取文件
最终payload:?start=%251$s&url=http://0177.0.0.1:5000/query?path=/fl4{file.name[3]}/flag
原理
为什么这里传入/fl4{file.name[3]}/flag
最后会变成fl4g/flag
呢
和guoke
师傅讨论了一下(guoke师傅ttttttql)
本题中,传入的path变量会调用file类
跟一下File类
可以发现把path变量赋值给了各个属性,本题中读取
filepath = (self.file.dir +'/{file.name}').format(file=self.file)
使用的是self.dir
类,我们把path
变量传给self.dir
会发生什么?
可以发现这里输出的dir
变成了/fl4{file.name[3]}
,而flag
则自动成为了flle.name
而这个变量会赋值给filereader
类中
filepath = (self.file.dir +'/{file.name}').format(file=self.file)的self.file.dir
变量拼接以后就成了/'fl4{file.name[3]}'+'/{file.name}'.format(file=self.file)
之前file.name
已经被赋值为flag
。则我们再次执行后name[3]
会自动截取file.name
的第四位赋值,变量成为了fl4g/flag
,即flag
路径
这里可以比喻成什么呢?
'{}{}'.format(1)
我们来本地测试一下
可以看到变量被成功拼接上了
也就得到了payload
总结
这道题的知识点比较多,感觉总体难度较高,是一道不错的题目
希望给各位师傅一些帮助
参考链接
https://yanluow.github.io/2020/02/29/python%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%A0%94%E7%A9%B6/
相关推荐: SonicWall SSL-VPN 远程命令执行
漏洞简介 由于 SonicWall SSL-VPN 历史版本(Sonic SMA < 8.0.0.4) 使用了受 Shellshock 漏洞影响的 bash 版本以及 HTTP CGI 可执行程序,攻击者可构造其恶意 HTTP 请求头,造成远程任意命令执…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论