hxp 36C3 CTF Web题 WriteupBin Writeup (Selenium模拟点击+Content Security Policy+Nonce+Parsley.js触发错误提示)

WriteupBin

– A web challenge from hxp 36C3 CTF

https://ctftime.org/event/825

题目部署

本地搭建:

解压WriteupBin.tar.xz,在Dockerfile所在目录下执行:

echo 'hxp{FLAG}' > flag.txt && < /dev/urandom tr -dc a-f0-9 | head -c 16 > writeup-id.txt && docker build -t writeupbin . && docker run --cap-add=SYS_ADMIN --security-opt apparmor=unconfined -ti -p 8001:80 writeupbin

访问127.0.0.1:8001

​ 这道题整个分析过程比题目本身更重要,所以我不会像普通的Writeup一样像个直通车每一步都走得特到位直抵flag,而是像走迷宫一样迂回式前进,每走一步都停下来分析,如果碰壁也要分析碰壁的原因。

题目分析

这道题基于这样一个发布和显示Writeup的平台。页面最上面可以浏览当前用户发布的WP;页面中间0f0e打头的这个字符串是当前session的用户id(并非PHPSESSID);下面的输入框可以写wp,点击submit提交后会跳转到show.php页面。

在这里插入图片描述

每一个wp都分配一个id,比如这里的73fd8aefbbc2768c,这个id值和get参数id的值是对应的,都是相同的16位hex。show.php里面显示出了wp的内容,当前用户可以点赞,还可以把wp展示给Admin用户。

好像光看这些不知道从何下手。

我们手头还有源码包。

这个题目的源码压缩包应该是作为题目的附件直接提供给做题者的,所以先来瞅一眼压缩包里能给我们什么样的提示。

.
├── Dockerfile							//Docker文件
├── admin.py								//使用selenium模拟admin登录并点赞
├── db.sql									//数据库文件
├── docker-stuff
│   ├── default							//配置文件
│   └── www.conf						//配置文件
├── www
│   ├── general.php					//连接数据库设置header头等一些初始化操作
│   ├── html
│   │   ├── add.php					//添加writeup相关操作
│   │   ├── admin.php				//把writeup提交给admin
│   │   ├── index.php				//入口文件
│   │   ├── like.php				//点赞操作
│   │   ├── login_admin.php	//admin登陆操作
│   │   └── show.php				//获取writeup内容
│   └── views
│       ├── header.php			//在页面上方展示目前id提交的writeup
│       ├── home.php				//页面中部用来提供给用户输入的界面
│       └── show.php				//点赞、提交给admin的展示页面
└── ynetd										//用来启动 admin.py

有一堆php,还有一个.py文件,一个Dockerfile,一个.sql的数据库文件等等。

我们先来看看题目是怎么部署的,也就是看看Dockerfile文件里有什么名堂。

COPY db.sql writeup-id.txt flag.txt /root/

可以看到flag文件是先从源码的根目录复制到了docker里的root目录下,

RUN replace '__FLAG__' "$(cat /root/flag.txt)" -- /root/db.sql

然后flag.txt里面的内容又被写到了root目录下db.sql这个数据库文件里,flag的真实值替换掉了数据库文件里flag的占位符__FLAG__

一同被写入db.sql的还有writeup_ID、数据库密码等等。

replace '__DB_PASSWORD__' "$(< /dev/urandom tr -dc A-Za-z0-9 | head -c32)" -- /root/db.sql /var/www/general.php && \

replace '__WRITEUP_ID__' "$(cat /root/writeup-id.txt)" -- /root/db.sql /var/www/html/admin.php && \

< /dev/urandom tr -dc A-Za-z0-9 | head -c32 > /root/admin-token.txt && \
replace '__ADMIN_TOKEN__' "$(cat /root/admin-token.txt)" -- /home/ctf/admin.py && \

replace '__ADMIN_HASH__' "$(php -r 'echo password_hash($argv[1], PASSWORD_DEFAULT);' -- $(cat /root/admin-token.txt))" -- /var/www/html/login_admin.php

再来看db.sql是如何处理这些写入的数据的:

值得关注的语句如下:

db.sql

USE `writeupbin`;
INSERT INTO `writeup` (id, user_id, content) VALUES ('__WRITEUP_ID__','admin','__FLAG__');

相当于Writeup_ID的值、“admin”、还有flag的值分别插入到了writeupbin数据库下writeup表中id、user_id、content这三个数据项下。

id user_id content
__WRITEUP_ID__的值 admin __FLAG__的值

顺着这个思维继续往前走,数据库里面的记录是如何被网页调用的呢?

我们来到 /var/www/html/show.php

$stmt = $db->prepare('SELECT id, content FROM `writeup` WHERE `id` = ?');
$stmt->bind_param('s', $_GET['id']); //防止SQL注入
$stmt->execute();
$writeup = mysqli_fetch_all($stmt->get_result(), MYSQLI_ASSOC)[0];

可以看到,show.php通过get请求参数‘id’获取到id号(这个id就是前面提到的每个wp的编号),然后把id的16位hex值代入sql查询语句,将writeup表的相关数据取出来存到$writeup变量里。

show.php底部包含了 …/views/show.php 这个文件

include('../views/show.php');

而$writeup变量就是在这里被调用的

<?= $writeup['content'] ?>,在/views/show.php页面里将id对应的content显示出来。

这下就明了了:拿flag的方法,就是输入admin的Writeup ID(唯一)作为show.php的get参数提交,这样从数据库取出的content就是flag的值,会在show.php页面里显示出来。可以这么理解:admin用户唯一的那个writeup的内容就是flag值。

但是怎么获取到admin的writeup id呢?

先说句题外话:对于数据库writeup表中非admin用户的记录,id和content两个字段存放的其实就是我们在index界面输入框提交的wp的编号和内容,user_id存放的是session id。

id user_id content
writeup的id $_SESSION[‘id’] writeup的内容

这个从add.php里可以体现出来:

$stmt = $db->prepare('INSERT INTO `writeup` (id, user_id, content) VALUES (?,?,?)');
$id = id();
$stmt->bind_param('sss', $id, $_SESSION['id'], $_POST['content']);
$stmt->execute();

总结一下:

Writeup数据表

写入数据库方式 用户 id(数据项) user_id(数据项) content(数据项)
docker部署时写入 admin用户 __WRITEUP_ID__的值(我们的 目标) admin FLAG
网页输入框提交 非admin 用户1(session1) Writeup 1-1的id (16位hex) $_SESSION[‘id’] Session 1 用户id (16位hex) Writeup 1-1的内容
网页输入框提交 非admin用户1(session1) Writeup 1-2的id (16位hex) $_SESSION[‘id’] Session 1 用户id (16位hex) Writeup 1-2的内容
非admin用户1(session1) Writeup 1-n的id (16位hex) Writeup 1-n的内容
网页输入框提交 非admin用户2(session2) Writeup 2-1的id (16位hex) $_SESSION[‘id’] Session 2 用户id (16位hex) Writeup 2-1的内容
网页输入框提交 非admin用户n(session n) Writeup n-1的id (16位hex) $_SESSION[‘id’] Session n 用户id (16位hex) Writeup n-1的内容

我们把目光重新聚焦到如何获取admin的id上来。

很容易想到的一个想法就是,index页面上会显示出当前session用户所撰写的所有wp的id,点进去就是一个个wp,如果我们把当前session的用户id改成admin,那么岂不是就能显示出admin的writeup id了吗?

这种可能性应该是没有的,要不然这个题目就太简单了。。。

保险起见还是分析一下。

我们看一下general.php,Session id就是在这里生成的。

function id() {
   
    return bin2hex(random_bytes(8));
}
...
if( ! isset($_SESSION['id'])) 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值