实验吧CTF——头有点大

[toc]

题目解析

解题链接:http://ctf5.shiyanbar.com/sHeader/

点开题目:

1562932892121

这题提示很明显,看它的要求,明显就是要修改http的请求头伪造身份。

所以就按照它说的,IE浏览器,.net9.9,英国访问

User-Agent加入:

  • compatible;MSIE 6.0 表示IE浏览器
  • .NET CLR 9.9 表示.NET 9.9 (注意开头那个点!)

Accept-Language加入

  • en-gb 表示英国地区

修改好后提交请求头得到flag:

1562933538453

本题知识点

请求头伪造

0x01 IP伪造

在TCP/IP中,我们可以伪造数据包来源IP,但是这样会导致发出去的数据包返回到伪造的IP上(你发件人地址填错了,别人给你发回来的时候肯定就按照错的地址发了)。

但是在应用层协议HTTP上实现就比较容易了,通过伪造IP可以让非授权IP访问服务器,绕过IP地址过滤,甚至可以钻服务器的漏洞进行攻击。

例如在投票系统中,为了防止刷票,往往会限制IP投票次数,比如一个IP只能投2票,那么如果伪造IP,就可以绕过IP限制,进行刷票。

IP伪造的关键是X-Forwarded-For

关于XFF这里不细讲,想了解更多的同学可以自行学习。我们直接讲它的应用。

它的格式如下:

X-Forwarded-For: client, proxy1, proxy2

XFF包含多个IP地址,每个值通过逗号+空格隔开,最左边是最原始的客户端的IP,后面如果有多层代理,那么每一层代理会在后面追加它自己的IP。

实际上,一般的客户端(例如浏览器)发送HTTP请求是没有XFF头的,当请求到达第一个代理服务器时,代理服务器会加上XFF请求头,并将值设定为客户端的IP地址。

但如果客户端在发起请求时就带了伪造的XFF头,那么后续的代理服务器就只会追加而不会覆盖,最终服务器获取的左边的第一个IP就是我们自己伪造的IP,就达到了伪造的目的。

0x02 User-Agent伪造

User Agent中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA。它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核等信息的标识。通过这个标识,用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计。比如说手机用户和电脑访问的网页排版样式等都是不一样的,这就是通过UA检测出来的。

UA的语法如下:

User-Agent: <product> / <product-version> <comment>

product代表软件产品名称,product-version表示版本,comment表示一些其他信息。具体UA语法可以参照这篇文章《User Agent 趣事谈》IETF标准文档,这里不展开讲了。

一般最常见的伪造就是伪造浏览器类型。有时候还要求伪造操作系统或者组件的版本等等,具体根据题目要求进行伪造。本题就要求特定的浏览器,以及特定的.NET版本。

一般常见的UA标识可以参考这篇文章《User-Agent大全》,内容实在太多了就不放在这儿了。

0x03 地区伪造

实际上地区伪造是在Accept-Language请求头中完成的。

Accept-Language请求头允许客户端声明它可以理解的自然语言,以及优先选择的区域方言。

AL的语法如下:

Accept-Language: <language>
Accept-Language: <locale>
Accept-Language: *

// Multiple types, weighted with the quality value syntax:
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

language:用含有两到三个字符的字符串表示的语言码。

locate:完整的语言标签。除了语言本身之外,还会包含其他方面的信息,显示在中划线(”-“)后面。最常见的额外信息是国家或地区变种(如”en-US”)或者表示所用的字母系统(如”sr-Lat”)。其他变种诸如拼字法(”de-DE-1996″)等通常不被应用在这种场合。

* :任意语言;”*”表示通配符。

;q= (q-factor weighting)紧跟在所标识语言的后面:

值代表优先顺序,用相对质量价值表示,又称为权重。q是权重系数,范围 0 =< q <= 1,q 值越大,请求越倾向于获得其“;”之前的类型表示的内容,若没有指定 q 值,则默认为1,若被赋值为0,则用于提醒服务器哪些是浏览器不接受的内容类型。


Examples of language tags

Language familyLanguage tagLanguage variant
Banglabn-BDBangla (Bangladesh)
bn-INBangla (India)
Chinesezh-CNMainland China, simplified characters
zh-TWTaiwan, traditional characters
zh-HKHong Kong, traditional characters
Dutchnl-BEBelgian Dutch
nl-NLStandard Dutch (as spoken in The Netherlands)
Englishen-GBBritish English
en-USAmerican English
en-CACanadian English
en-INIndian English
en-AUAustralian English
en-NZNew Zealand English
Frenchfr-BEBelgian French
fr-CH“Swiss” French
fr-FRStandard French (especially in France)
fr-CACanadian French
Germande-ATAustrian German
de-DEStandard German (as spoken in Germany)
de-CH“Swiss” German
Italianit-CH“Swiss” Italian
it-ITStandard Italian (as spoken in Italy)
Portuguesept-PTEuropean Portuguese (as written and spoken in Portugal)
pt-BRBrazilian Portuguese
Spanishes-ESCastilian Spanish (as spoken in Central-Northern Spain)
es-MXMexican Spanish
es-ARArgentine Spanish
es-COColombian Spanish
es-CLChilean Spanish
es-USAmerican Spanish
Tamilta-INIndian Tamil
ta-LKSri Lankan Tamil

实验吧CTF——后台登录

[toc]

题目解析

解题链接:http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php

一进去给个登录界面:

1562929066459

F12打开源码看看,哦吼,给了php代码提示:

1562929144304

SELECT * FROM admin WHERE username = 'admin' and password = 'xxx' 

这个xxx就是md5($password,true),

这个语句很明显就是sql注入。那么我们就要构造xxx为 ‘ or ‘ 1…这种形式,让后面变成true。

构造password为ffifdyop,输出结果就为:’or’6�]��!r,��b

我们发现有 ‘ or ‘ 6…(后面乱码不用管)。这样就成功注入。

得到flag:

1562929036492

其实如果你仔细看这道题的url,你会有神奇的发现。

本题知识点

md5注入

md5(string,raw)

参数描述
string必需。规定要计算的字符串。
raw可选。规定十六进制或二进制输出格式:<br />true – 原始16字符2进制格式<br />false – 默认。32字符十六进制数

ffifdyop这个字符在16字符2进制格式中会变成一个特殊的格式

> ‘or’6�]��!r,��b

可以进行sql注入。

实验吧CTF——PHP大法

[toc]

题目解析

解题链接:http://ctf5.shiyanbar.com/DUTCTF/index.php

进入界面,只有简单的一句话:

1562926789229

给了个提示,叫index.php.txt,那就看看呗。url加个.txt,得到了php源码

具体源码如下:

&lt;?php
if(eregi(&quot;hackerDJ&quot;,_GET[id])) {
  echo(&quot;&lt;p&gt;not allowed!&lt;/p&gt;&quot;);
  exit();
}_GET[id] = urldecode(_GET[id]);
if(_GET[id] == &quot;hackerDJ&quot;)
{
  echo &quot;&lt;p&gt;Access granted!&lt;/p&gt;&quot;;
  echo &quot;&lt;p&gt;flag: *****************} &lt;/p&gt;&quot;;
}
?&gt;


&lt;br&gt;&lt;br&gt;
Can you authenticate to this website?

分析源码,它判断id是不是hackerDJ,如果是就显示“not allowed!”,然后对id进行解码再次判断它是不是hackerDJ。是了就返回flag。

这个题其实比较简单,就是输入的原始参数id不能为hackerdj(eregi()判断的时候不区分大小写),然后解码之后要让值为hackerDJ。由于浏览器url在传递参数的时候会进行一次decode,然后在源码里又进行一次decode,所以我们在构造id的时候应该encode两次。

网上在线url编码hackerDJ还是hackerDJ,送浏览器里不认,所以我们需要手动encode。

为了简单,就把开头的h编码两次就可以。

hackerDJ->%68ackerDJ->%2568ackerDJ

(当然你把68也encode,最后变成 %25%36%38ackerDJ 也是可以的。当然你也可以把所有的字符都进行两次encode,只要你不嫌麻烦)

传入参数id,得到flag

1562928157030

本题知识点

URL编码

巨简单,就是%加上字符对应的ASCII码(16进制)。

实验吧CTF——天网管理系统

[toc]

题目解析

解题链接: http://ctf5.shiyanbar.com/10/web1/

首先进入题目界面

1562823085465

先点一下登陆系统,又跳回到这个界面,看来用户名密码不对(废话)

那看看源码

1562823163856

哦吼,看到一个php的提示,要MD5加密后的$test == ‘0’,那这很常见啊,随便找一个MD5加密后是0e开头的就行了。

给个QNKCDZO(加密后的结果是0e830400451993494058024219903391)试一下

1562824378658

出来个这,我开始没仔细看,天真的以为flag已经出来了,把hjkleffifer当flag了😅

仔细一看,哦,原来是url后缀,好,把index.php改成出现的后缀,有了新发现

1562824822673

嗯,又是一段php代码,具体代码如下

$unserialize_str = $_POST['password'];
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' && $data_unserialize['pass'] == '???'){
    print_r($flag);
}

就是说要通过password传递参数让data_unserialize数组中的user项和pass项满足条件。但是现在不知道两处???是什么,所以没法直接构造相等的值。

但是还有一个提示。“成也布尔,败也布尔”。bool类型的true可以和任意字符串弱类型相等。所以我们只要让user和pass都为bool类型的ture就行了。

构造password的值:a:2:{s:4:”user”;b:1;s:4:”pass”;b:1;}

a代表array,s代表string,b代表bool,数字代表个数/长度(具体请参照反序列化知识点)。

提交,拿到flag。

1562825962891


本题知识点

PHP弱类型

PHP比较相等性的运算符有两种,一种是strict,一种是non-strict。”===”和”!==”即strict比较符,只有在类型相同时才相等。”==”和”!=”即non-strict比较符,会在类型转换后进行比较。

看以下的例子:

 true
    var_dump("1" == "01"); // 1 == 1 -> true
    var_dump("10" == "1e1"); // 10 == 10 -> true
    var_dump(100 == "1e2"); // 100 == 100 -> true
    var_dump(0 == '0'); // true
    var_dump(0 == 'abcdefg'); // true 
    var_dump(0 === 'abcdefg'); // false
    var_dump(1 == '1abcdef'); // true
?>

在和整数比较的时候,会把另一个参数强制转换成整数:

  • 如果参数是字符串,返回字符串中第一个不是数字的字符之前的数字串所代表的的整数值。
  • 如果字符串第一个是‘-’,则从第二个开始算起。
  • 如果参数是浮点数,则返回它取整后的值。

a是纯字符串,所以被转换成0。1abcdef被转换成1。

而e在比较的时候会被视为科学计数法,10的次方,1e2就是10的2次方。

关于布尔值的比较,见下表:

松散比较 ==

TRUEFALSE
1TRUEFAUSE
0FAUSETRUE
-1TRUEFAUSE
“1”TRUEFAUSE
“0”FAUSETRUE
“-1”TRUEFAUSE
NULLFAUSETRUE
array()FAUSETURE
“php”TRUEFAUSE
“”FAUSETRUE
TRUETRUEFAUSE
FALSEFAUSETRUE

从上表可以看出

当转换为bool型时,以下值被认为是FAUSE:

  • FAUSE本身
  • 整型值0
  • 空字符串
  • 字符串”0″
  • 不包括任何元素的数组(注意,一旦包含元素,就算包含的元素只是一个空数组,也是true)
  • NULL
  • 从空标记生成的SimpleXML对象

其余所有值都认为是TRUE。

具体请参见PHP类型比较表

所以当MD5加密后出现0exxxxxxxxxxxxxxxxxxxxxx之类的,如果用non-strict比较符,php就会当成0的多少多少次方(那当然结果还是0),和0比较就为true。因此可以利用这个漏洞进行验证绕过。

MD5加密后开头为0e的字符串举例:

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s1885207154a
0e509367213418206700842008763514

s1502113478a
0e861580163291561247404381396064

s1885207154a
0e509367213418206700842008763514

s1836677006a
0e481036490867661113260034900752

s155964671a
0e342768416822451524974117254469

s1184209335a
0e072485820392773389523109082030

s1665632922a
0e731198061491163073197128363787

s1502113478a
0e861580163291561247404381396064

s1836677006a
0e481036490867661113260034900752

s1091221200a
0e940624217856561557816327384675

s155964671a
0e342768416822451524974117254469

s1502113478a
0e861580163291561247404381396064

s155964671a
0e342768416822451524974117254469

s1665632922a
0e731198061491163073197128363787

s155964671a
0e342768416822451524974117254469

s1091221200a
0e940624217856561557816327384675

s1836677006a
0e481036490867661113260034900752

s1885207154a
0e509367213418206700842008763514

s532378020a
0e220463095855511507588041205815

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s214587387a
0e848240448830537924465865611904

s1502113478a
0e861580163291561247404381396064

s1091221200a
0e940624217856561557816327384675

s1665632922a
0e731198061491163073197128363787

s1885207154a
0e509367213418206700842008763514

s1836677006a
0e481036490867661113260034900752

s1665632922a
0e731198061491163073197128363787

s878926199a
0e545993274517709034328855841020

PHP关联数组

在本题中我们看到数组的下标不是数字,而是字符串,这是为什么呢?原来这是PHP的关联数组,如果你学过Python,那么你可以类比Python中的字典。PHP关联数组是使用用户分配给数组的指定键的数组。

//php中有两种创建关联数组的方法
//第一种
age=array("Bill"=>"35","Steve"=>"37","Elon"=>"43");
//第二种age['Bill']="63";
age['Steve']="56";age['Elon']="47";

然后在脚本中可以使用指定键:

"63","Steve"=>"56","Elon"=>"47");
echo "Elon is " . $age['Elon'] . " years old.";
?>
//运行结果:
//Elon is 47 years old.

如需遍历并输出关联数组的所有值,可以使用foreach循环:

"63","Steve"=>"56","Elon"=>"47");

foreach(age asx=>x_value) {
  echo "Key=" .x . ", Value=" . $x_value;
  echo "
"; } ?> /*运行结果: Key=Bill, Value=63 Key=Steve, Value=56 Key=Elon, Value=47 */

PHP中的序列化和反序列化

“序列化”,听着好像hin高大上,但实际上当你了解它之后会发现,其实它并不可怕。实际上PHP所谓的序列化实际上就是将各种类型的数据,压缩并按照一定格式存储。说白了就是压缩。使用的函数是serialize()。

看以下实例:

pri = private;
    }
    public function get_pri()
    {
        returnthis->pri;
    }
}

obj = new test();obj->set_pri('Active');
data = serialize(obj);
echo $data;

运行实例得出的结果是:

O:4:”test”:3:{s:9:”testpri”;s:6:”Active”;s:6:”*pro”;s:5:”test1″;s:3:”pub”;s:5:”test2″;}

看着好吓人!让我们一点一点看,慢慢你就会发现它其实很好理解。

Untitled (Draft)-3

如果你足够细心,你会发现private里明明我的属性名是pri,为什么这里是testpri,而且testpri不应该是7个字符吗,为什么这里是9个字符?protected中的名字和字符数也不一样。

这其实涉及到PHP的属性的访问权限问题,具体可以参照这篇文章:《一篇文章带你深入理解漏洞之PHP反序列化漏洞》。这里不做深入。我们只需要记住以下几点即可:

(1)Public权限

正常该是几个字符就是几个字符

(2)Protected权限

16进制打开看到属性名的格式为:

%00*%00属性名

在属性名前有一个 ***** ,而且 * 两边各有一个%00,每个%00占一个字符,所以字符数多了2。

(3)Private权限

16进制打开看到属性名格式为:

%00类名%00属性名

在属性名前面有这个类的名字,两边各有一个%00,所以字符数多了2。

这个特性必须记住,否则后期我们在构造或修改我们的攻击语句的时候就很容易出错。

如果你再细致一点,就会发现这个类不但有属性,还定义了许多的方法。可是序列化的只有属性,没有方法。这是为什么?

PHP的序列化只序列化属性,不序列化方法,这个性质引出了两个重要话题:

(1)在反序列化的时候一定要保证在当前的作用域环境下有该类存在

(2)反序列化攻击也就是依托类属性进行攻击

  • 因为序列化没有方法,所以我们只能控制类的属性,所以类属性是我们唯一的攻击入口。我们寻找合适的能被我们控制的类属性,然后利用它本身存在的方法,发动攻击。

看一个例子:

test = new L();
    }

    function __destruct()
    {
        this->test->action();
    }
}

class L
{
    function action()
    {
        echo "welcome to my PHP";
    }
}

class Evil
{
    vartest2;
    function action()
    {
        eval(this->test2);
    }
}

unserialize(_GET['test']);

这段代码就存在可以利用的漏洞。

在分析这段代码之前,我们首先看以下方法:

  1. construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
  2. wakeup() :unserialize()时会自动调用。
  3. destruct():当对象被销毁时会自动调用。
  4. toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用。
  5. get() :当从不可访问的属性读取数据。
  6. call(): 在对象上下文中调用不可访问的方法时触发。

我们发现有很多方法会被自动调用。这就给我们了可乘之机。回到上面的例子,我们分析这段代码,从上往下看,test类在构造对象时让test变量指向一个L的对象,然后在test对象销毁时调用了L对象的action()。

首先看我们可以控制的参数,也就是可以利用的只有$test,也就是test类,那么我们接着分析,__destruct()函数会在对象销毁时自动调用,那么它就会执行action()。我们再看在action()执行过程中有没有可以利用的,发现Evil类里面也有action(),而且它调用了eval(),那这样我们就有了攻击思路。

我们可以修改test变量的值,让它指向Evil的对象,这样在test对象销毁调用destruct()时就会调用Evil对象的action(),就会执行eval(),同时我们还可以再构造test2变量的payload,这样就可以进行攻击。

那么我们就可以构造序列化结果:

test = new Evil //让它指向Evil
    }
}
class Evil
{
    var test2 = "phpinfo();"; //payload为phpinfo(); 
}data = new test;
data = serialize(data);
echo $data

得到结果:

O:4:”test”:1:{s:10:”testtest”;O:4:”Evil”:1:{s:5:”test2″;s:10:”phpinfo();”;}}

将它送入$test,实现攻击。特别注意在构造的时候不要忘了两个%00

1562905573925

提交就可以执行我们的payload

1562905711077

这是一个很简单的示例,PHP反序列化漏洞的知识和利用方式还有很多,如果要深入展开实在是太多了。如果读者有兴趣可以参考这篇文章:《一篇文章带你深入理解漏洞之PHP反序列化漏洞》