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'])) {$_SESSION = ['id' => id(), 'c' => id()];
}

可以看到session id是后端随机生成的,好像不可控。

但是浏览代码,发现在login_admin.php中有为$_SESSION[‘id’]赋值的操作。

if (!isset($_SERVER['PHP_AUTH_USER'])) {header('WWW-Authenticate: Basic realm="admin.py"');header('HTTP/1.0 401 Unauthorized');exit();
}
if ($_SERVER['PHP_AUTH_USER'] === 'admin' && password_verify($_SERVER['PHP_AUTH_PW'], '__ADMIN_HASH__')) {$_SESSION['id'] = 'admin';redirect('/show.php?id='. $_GET['id']);
}

首先这里可以看到有个Basic认证,提示信息为admin.py,正好源码里面有个文件也叫这个名字。admin.py有什么用后面会讲解。

$_SESSION[‘id’]变为admin的前提有二:

  1. Basic认证输入的用户名(即$_SERVER[‘PHP_AUTH_USER’]) 为 ‘admin’
  2. password_verify($_SERVER['PHP_AUTH_PW'], '__ADMIN_HASH__')为真,即Basic认证输入的密码为__ADMIN_HASH__

而Docker在部署时无论密码原文__ADMIN_TOKEN__还是密码哈希__ADMIN_HASH__的值就已经被确定了,我们无从得知。所以我们想凭一己之力让$_SESSION[‘id’]变为admin是不可能的。

总结下来就是,session id不可控。

那么我们作为普通用户还能够怎么操作从而跟admin用户建立起联系呢?

想到在Writeup详情页面(也就是show.php?id=<writeup id>)中有一个Show to admin的按钮,按钮上方Liked by显示的是用户给这篇writeup的点赞情况。

点击Show to admin后两三秒钟内回到刚刚的页面可以看到点赞情况还没有发生变化,但是再过一小会儿刷新页面就可以看到Liked by名单里就已经出现了admin的名字。

所以我们有两点收获:

  1. 透过Show to admin按钮和admin的反馈,我们可能能够建立起普通用户与admin之间的联系,这极有可能是突破口。
  2. admin用户的点赞并非Show to Admin按钮触发后的即时反应,存在延时,这里面可能有一个触发机制。

我们回到源码,来看Show to Admin和admin点赞背后的逻辑。

/views/show.php 表单提交

<form method="post" action="/admin.php"><input type="hidden" name="c" value="<?= $_SESSION['c'] ?>"><input type="hidden" name="id" value="<?= $writeup['id'] ?>"><input type="submit" value="Show to Admin">
</form>

c和id两个参数被POST提交,其中c参数的值为$_SESSION[‘c’] 的值,而$_SESSION[‘c’] 与$_SESSION[‘id’]一样都是随机生成的16位hex;id参数的值就是Writeup ID。两参数提交后跳转到admin.php里,

admin.php

$fp = stream_socket_client('tcp://127.0.0.1:1024', $errno, $errstr, 2);
if (!$fp) {die('admin seems to be too busy');
} else {fwrite($fp, $_POST['id']."\n");fclose($fp);
}

而admin.php通过tcp连接,把 $_POST[‘id’] 也就是Writeup ID写到数据流中去。

数据流流向何处呢?Dockerfile里面有这样一句:

CMD ynetd -lm -1 -lt 5 -t 60 -lpid 256 -sh n /home/ctf/admin.py

执行了ynetd这个ELF文件,-lm -1 -lt 5 -t 60 -lpid 256 -sh n /home/ctf/admin.py都是ynetd的参数,最后一个参数是admin.py。此处这个ynetd的功能就是启动admin.py并将tcp流作为py文件的标准输入流。连贯起来就是,admin.php通过数据流把writeup id传给了admin.py。

再来看admin.py。

首先肯定要接收点击Show to Admin后输入流传过来的writeup id。

writeup_id = input().strip()

总的来说,Admin.py的功能就是通过selenium来模拟admin用户点赞的操作。

Selenium 是一个用于Web应用程序的自动化测试工具,Selenium会生成一个浏览器的环境,模拟浏览器的行为,就像真正的用户在操作一样。很多人用它来写爬虫。

admin.py

display = Display(visible=0, size=(800, 600))
display.start()
chrome_options = Options()
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
driver = webdriver.Chrome('/usr/bin/chromedriver', options=chrome_options)url = 'http://admin:__ADMIN_TOKEN__@127.0.0.1/login_admin.php?id='+writeup_id
driver.get(url)
element = driver.find_element_by_xpath('//input[@id="like"]')
element.click()driver.quit()
display.stop()

很容易理解,先建立一个显示窗口,加入一些chrome访问的选项,然后驱动一个chrome浏览器去以admin身份访问含特定Writeup ID的login_admin.php。

然后通过find_element_by_xpath这个函数,找到id为like的第一个input标签,即点赞按钮的标签,然后模拟点击点赞按钮。

/views/show.php

<form method="post" action="/like.php"><input type="hidden" name="c" value="<?= $_SESSION['c'] ?>"><input type="hidden" name="id" value="<?= $writeup['id'] ?>"><input id="like" type="submit" value="												

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

  1. ctf的web题目php,32C3 CTF 两个Web题目的Writeup

    0x00 简介 作为一个销售狗,还能做得动Web题,十分开心. 这次搞了两个题目,一个是TinyHosting,一个是Kummerkasten. 0x01 TingHosting A new file ...

  2. CTF Web题 部分WP

    1.web2 听说聪明的人都能找到答案 http://123.206.87.240:8002/web2/ CTRL + u 查看源代码 2.计算器 http://123.206.87.240:8002 ...

  3. CTF平台题库writeup(一)--南邮CTF-WEB(部分)

    WEB题 1.签到题 题目:key在哪里? writeup:查看源代码即可获得flag! 2.md5 collision 题目: <?php $md51 = md5('QNKCDZO'); $a ...

  4. i春秋python_i春秋CTF web题(1)

    之前边看writeup,边做实验吧的web题,多多少少有些收获.但是知识点都已记不清.所以这次借助i春秋这个平台边做题,就当记笔记一样写写writeup(其实都大部分还是借鉴其他人的writeup). ...

  5. CTF web题 wp:

    1.签到题 火狐F12查看源码,发现注释: 一次base64解码出flag 2.Encode 在这里插入图片描述 和第一题界面一样?? 轻车熟路f12: 发现编码: 格式看上去是base64,连续两次 ...

  6. python selenium模拟点击

    文章目录 1.安装谷歌浏览器 2.安装浏览器驱动 3.安装selenium 4.简单测试 5.打卡程序 6.linux设置定时任务 7.其他 1.安装谷歌浏览器 #下载包 wget https://d ...

  7. 【Python】Selenium模拟点击网页下载文件

    整个流程大致如下: 1.首先需要在http://chromedriver.storage.googleapis.com/index.html中下载chrome浏览器版本对应的驱动文件,可以在浏览器[设 ...

  8. BSides Noida CTF 2021 web题wowooofreepoint writeup(两道反序列化)

    emmm终于开始正经地写第一篇wp了!撒花撒花~这场比赛也算是我第一个没爆零的比赛,自己独立做出来了一道(半?),拿到flag也是相当开心~(当然还是比较菜,大佬轻喷)比完赛之后自己又去把差一点做出来 ...

  9. mysql 南邮ctf_南邮ctf web题记录(上)

    1.签到题 f12看源码就行了. md5 collision 题目贴出了源码,按照题意,a不等于QNKCDZO但是md5与QNKCDZO的md5相等时就可以获得flag. 如果两个字符经MD5加密后的 ...

最新文章

  1. Mysql 源码安装
  2. Io 异常: The Network Adapter could not establish the connection解决方案
  3. 网格自适应_Abaqus网格重划自适应技术
  4. 如何删除子域信任关系?
  5. jeesite缓存问题
  6. 浅析C#中构建多线程应用程序
  7. K8S 的报错问题解决
  8. 使用原生Java代码生成可执行Jar包
  9. Elasticsearch写入webshell漏洞(WooYun-2015-110216)
  10. 计算机cpu电压,怎么样调电脑cpu电压啊
  11. 保护系统 WinXP故障恢复控制台完全指引
  12. 写给30岁的自己,以及所有即将、正在、已经奔三的朋友们
  13. 冒志鸿:没有对比就没有伤害,原来中国的区块链这么……
  14. slides.com 导出PDF
  15. 移动端高清、多屏幕适配方案
  16. 易语言可以编译c语言,刷屏软件?其实易语言也可以做这种软件
  17. 解决Ubuntu 20.04 播放视频,因缺少编解码器无法处理音频/视频流,以及解决‘因没有公钥,无法验证下列签名’问题
  18. 华为云服务器(Centos7)安装与卸载mysql8
  19. 到微软下载VS2008
  20. Linux 性能监控工具命令大全

热门文章

  1. 网络七层模型与物联网三层模型
  2. kvm创建uefi虚拟机
  3. ct图像的特点以及CT相关
  4. C语言文件操作(fopen,fclose)
  5. 上海纽约大学2020年本科招生简章正式公布,最优一本线!
  6. 广义预测控制(GPC,含公式推导和仿真截图)
  7. Python实现knn分类算法(Iris 数据集)
  8. linux博通2070蓝牙驱动,Broadcom博通BCM 2070/20702系列蓝牙设备驱动怎么样
  9. 面料淡季就是服装的旺季
  10. 川普无法结束俄罗斯对美国的软件战争