CTF题解记录-web篇

前言

好好学习,天天向上,目录如下:

WEEK 1

  • 加了报错的注入
  • 后台登录
  • 上传绕过

WEEK 2

  • 没时间解释了
  • welcome to bugkuctf
  • never give up

WEEK 3

  • shrine
  • SSTI
  • 沙箱逃逸

WEEK 4+5

  • Flask真香
  • Flask Plus
  • 基本操作

WEEK 1

加了报错的注入

实验吧:http://ctf5.shiyanbar.com/web/baocuo/index.php

  1. 查看页面源码,注释说明有sql注入
  2. 然后测试一下username和password,发现有过滤
  3. bp简单爆破一下过滤字符,得出结果:
    username过滤了( ) = #等字符以及order by等关键字, password过滤了# = ; 等字符以及updatexml()ext()两个报错函数。
  4. 在伟大的搜索引擎的帮助下,我们查到了这两个报错函数的信息,顺便搜到了注入格式。
  5. 爆数据库测试一下可用
  6. 然后爆表名,列名,flag,注意到=被过滤,换LIKE,结果LIKE也被过滤,于是换上了正则regexp,同样show也被过滤。
1
2
3
4
5
6
7
8
9
payload:

username=1' and updatexml/*&password=*/(1,concat(0x24,(select database()),0x24),1) and '1

username=1' or updatexml/*&password=*/(1,concat(0x3b,(select group_concat(table_name) from information_schema.tables where table_schema regexp database())),1) and '1

username=1' or updatexml/*&password=*/(1,concat(0x3a,(select group_concat(column_name) from information_schema.columns where table_name regexp 'ffll44jj' )),1) and '1

username=1' or updatexml/*&password=*/(1,concat(0x3a,(select value from ffll44jj)),1) adn '1

看了dalao的wp后发现password并没有过滤exp()函数可以直接利用password构造payload,不用分割注入。

flag{err0r_b4sed_sqli_+_hpf}

上传绕过

实验吧:http://ctf5.shiyanbar.com/web/upload/

  1. 这道题看上去是一个文件上传漏洞,查看源码,注意到了hidden表单,感觉有用。
  2. 按要求传文件,报错要jpg,传jpg又报错要php,估计是有两次验证。

  3. 直接字符截断没有用,想起那个表单,估计是要构造路径截断。
  4. 构造路径,进hex改包再发即可得flag!

    flag{SimCTF_huachuan}

后台登录

实验吧:http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php

  1. 源码有代码,估计是要构造md5注入。
  2. 怎么构造?
  3. 怎么构造?
  4. 怎么构造?
  5. 算了看wp吧
  6. ……
  7. 直接用文件名ffifdyop构造md5有了'or'xxxx'的语句,注入成功,告辞。

flag{ffifdyop_has_trash}


WEEK 2

1. 没时间解释了

moctf: http://119.23.73.3:5006/web2/index2.php

抓包阻止重定向->路径构造->时间竞争

常规性的F12查看源码,并没有找到什么有用的信息,测试一波.svn.git的源码泄露,仍然并没有什么有用的信息,遂放弃。

自闭一段时间后,观察到urlindex2.php觉得可能有问题,然后尝试访问index.php,重定向回了index2.php,于是改用BP截包,得到index.php返回如下:

uploadsomething

访问uploadsomething.php,令人熟悉的表单映入眼帘,随意输入后显示:

估计是一个路径构造,写了php一句话webshell并没有连上,试着访问了一下返回Too Slow,根据标题和该返回猜测是一个时间竞争,同时观察到不同提交返回的base64的路径是相同的,那么可以写脚本去构造路径并读取。
然而……

脚本都是Too Slow!这不合理啊!后来用BP Intruder持续抓包,获得flag,至于脚本比BP慢这实在没有想到。

2. welcome to bugkuctf

bugkuctf: http://123.206.87.240:8006/test1/

php协议->文件包含->php反序列化

这道题小硬核。
常规F12得到源码:

尝试直接访问hint.php无果,注意到源码中文件包含函数file_get_contents(),
于是可以利用php协议操作数据流的方法,将字符串构造文件利用php://input上传$user,再利用php://filter获取返回的数据里,相关参考PHP官方文档:http://php.net/manual/zh/wrappers.php.php
php://filter相关信息:

利用这个方法我们可以这样构造payload

从而获得index.phphint.php的源码base64:

继续代码审计。

观察得到这是一个php反序列化漏洞,利用在hint.php中得到的类名为Flag,使用的是__toString魔法函数,由此我们可以构造反序列化payloadO:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
并得到flag

3. never give up

bugkuctf: http://123.206.87.240:8006/test/hello.php

php任意文件读取->解码*3->字符截断

这道题比较逗逼。
常规F12得到两行代码,带入注释里的1p.html发现重定向到了BugkuCTF的首页,于是Postman抓包得到如下结果:

URL解码一把梭!

base64解码一把梭!

又是URL?一把梭!

经过三次大起大落的编码转化,最后得到源码:

注意到代码中有两处矛盾:

  1. $id即是0又不是0
  2. $b的首位字符必须是4又不能是4

对于前者,采用php松散比较,利用'aaa' == 0的特性绕过,对于后者,采用字符截断(对于php版本的判断可以根据$data实现,这里已确定可截断)

那么!那么!那么!
就阔以构造粗配楼德payload:
id=aaa&a=php://input&b=%00lutyhhh

得到flag

似乎安排得明明白白…直到我看到其他大佬的wp…

我的表情:

看来似出题人忘记屏蔽隔离了,不然这题也是小硬核。

WEEK 3

Shrine

Tokyo Western CTF 2018
Link: https://github.com/CTFTraining/westerns_2018_shrine
Python模板注入 Python沙箱绕过 flask审计

因为了解Python方面的审计去网上找到了这道CTF题目,这道题是用docker本地复现的,因为涉及到不会的知识点,所以到一半就照着WP继续了。

1. 代码审计

这道题目非常直接,进入就是代码,在调整格式后我们可以得到源码如下:

从源码可知:

  1. 设定了两个路由,一个路由读取文件源码,另一个路由读进一个字符串对象。
  2. 第二个路由中读进的字符串,经过了函数safe_jinja的过滤,过滤函数删除了()并强行将configself字符设为空。

这时候思路已经比较明确了,要么读config['FLAG']要么读os.envrion.pop('FLAG'),但因( )被过滤第二个显然不行,configself都被强制置空,那么就只能想别的方法,然而我没有想到。

SSTI

继续分析元源码,程序利用模板向后端传入了变量,因此存在模板注入(SSTI),我们可以简单构造一个Poc验证:

在这里现补充模板的相关知识,模板用于将服务端返还内容渲染成前端页面,为了分离前端代码和后端变量,模板中设定 {{ }}包括的内容为后端变量,{% %} 包括的内容为逻辑语句。

相关模板类型的Poc可以参考下图:

沙箱逃逸

之后参考了一个大佬的WP(tokyo-westerns-ctf-2018-web-wp)
,学习到了一些骚炫的姿势,这里复现一下他的思路

  1. 既然configself都被置空了,那么只能用其他方法读取到这个全局变量,就需要参考Flask框架的文档,这里应用到了python沙箱逃逸的方法,就我理解这方法就是利用python对象之间的引用关系来调用被禁用的函数对象,文中提供了一个flask上下文本地变量current_app,使用方法__globals__['current_app'].config['FLAG']。,通过在尝试这个payload后的到以下结果:

显然不能直接访问__globals__全局变量,目测有WAF需要再次逃逸。

  1. 文中又提供了两种方法:get_flashed_messagesurl_for这两个函数添加了current_app的引用,于是构造:url_for.__globals__['current_app'].config['FLAG']
    最后payloadhttp://127.0.0.1:8081/shrine/
    获得flag:

在沙箱逃逸这一块的还有很多问题,比如怎么查到各对象之间的引用关系,哪些对象在哪些作用域有效,这里只积累了一个思路,以后需要在挖掘。

PS:刚刚写完SSTI的内容,hexo deploy的时候报错,一查结果是前面模板的花括号导致了解析,妙啊妙啊。