web安全入门

序:环境搭建

注:原创文章,禁止转载

下载一个老版本的phpstudy,(小皮那个也应该可以)。

网站文件放在/phpstudy/WWW/目录下

常见错误:

  • 80端口被占用的可以选择其他端口8080,9096等,或者去把win+r输入services.msc,停掉SQL Reporting那个服务

  • 提示3306端口被占用,很有可能是你以前安装过mysql,phpstudy会自动提供一个mysqld的服务,导致两者冲突,打开任务管理器,找到mysqld.exe,结束,再启动phpstudy的环境即可

  • 网站还是不能访问的,找到其他选项,点击,找到phpStudy设置,点击允许目录列表

然后登陆phpmyadmin(默认账户密码root,root)导入sql文件,创建数据库,然后再把网站里的sql文件导入

可以自行百度找的一些网站源码来练习:如sqli-labs,dvwa,pikachu,upload-labs等

此处先讲一些简单的漏洞。

注:如果你熟悉docker的话,推荐使用docker一键拉取靶场,相关文章:

docker简介和常用命令

docker搭建pikachu

docker搭建dvwa

docker搭建sqli-labs

本文最后更新于:2022-01-06

1.sql注入

1.1引入

  • 注入的根本原因:没有对用户的非法输入做过滤,如#,(),and,union select 等sql语句关键字

sql注入的根本性思路:构造输入使在源码中的sql查询语句闭合且语句为真,下面将在联合注入详讲

观察下面PHP源码中的一个登录时的sql查询语句:

$query="select * from ".$t_name." where id='".$id."'"." and pass='".$pass."'";

当我们随便输入一个用户名和密码时,在数据库中查询不到匹配的项,那么登录失败,那么通过sql注入登录是如何实现的呢?

我们在用户名处输入(我们通常称之为万能密码,万能密码并不唯一,且构成方式较多):

'or 1=1 #

密码随便输入或者不输入,那么会是什么效果呢?

登录成功.jpg(请读者自行脑补)

我们登录成功了,那么我们来分析一下原因,输入'or 1=1 #后sql查询语句变为了(PHP中 . 用于连接字符串):

select * from ".$t_name." where id='   ' or 1=1 #      '"." and pass='".$pass."'

前面id 通过我们构造的 '闭合,#注释掉了后面的 ' 以及密码的查询验证,而只剩下的 or 1=1这个查询条件是为真的,这就是我们前面讲到的注入的两个关键点:

  • 闭合

  • 为真

虽然你可能懂了,但随着不断的练习,你会对上面的这四个字会有更深刻的理解。

那我们怎么知道它是' 闭合呢,在真实网站中我们没有上帝视角只能不断尝试,尝试 ' ,尝试" ,尝试"),'),甚至")),'))等等(一般来说没有哪个神经病会把sql查询语句写成(((' '))),论开发者的自我修养)

那么,一般网站哪些地方存在注入呢?首先肯定要与数据库有交互的地方,像是登录,查询界面之类的,一般来说登录是post方式提交,查询是get方法提交。

 

在讲两种提交方式之前我们先来了解一下url的构成。

简单来说我们通常看到的一个网站链接都是一个url,类似http://www.test.com/item?id=1

  • http是通信协议,一般为http,https(https安全性更高)

  • www.baidu.com/a/b部分为 域名或者IP+路径 (在域名后面有一个默认80端口,可不写,若不是默认端口则需要写出)

  • ?后面接的是get参数,参数为id,值为1

 

简单来说,get方法就是有明文显示,安全性较差,如:http://www.test.com/item?id=1,是直接将参数暴露在地址栏的

而post提交,地址栏是看不到参数和值的,通常登录输入用户名和密码你并不能在地址栏看见那些参数和值,就是用的post提交,用比较官方的话来说,post操作对用户来说是不可见的,post提交若没有加密,可以使用burpsuite抓包直接看到post提交的数据,这个在后面讲到burpsuite的时候我们会讲到。

我们不如网上讲的那么精细,总结起来就是:get和post都只是一种传递数据的方式,get也可以把数据传到服务器,本质都是发送请求和接收结果,只是组织格式和数量上有差别。(要看详细对比的,可以参考这篇文章[https://www.cnblogs.com/logsharing/p/8448446.html])

最后,对于真实网站来说,一般都有WAF,web应用防护系统,如果你尝试注入,发现它过滤了那些关键字,在尝试过一些绕过方法后还是无法绕过后,那么可以放弃了,可以去考虑挖掘其他漏洞,千万不要与其死磕。

 

1.2 联合注入

联合注入,思路:

  • 1.找到一个可以尝试注入的地方

  • 2.判断参数类型(确定闭合方式)

  • 3.order by 判断字数

  • 4.判断有无输出位置(根据正常操作,网页给出的反馈判断有无输出位置)

  • 5.有输出位置,尝试union select爆输出(将参数设置为一个无法查到的值如-1,999999)

1步骤就不必多说了。

2,参数类型共有三种,数值型,字符型,搜索型,这里我们只讲前两种

直接在参数的值后面输入and 1=1不报错,那么一般来说就是数值型(但是真实网站有过滤,你输入了这些页面也许还是显示正常)

直接在参数的值后面输入' 或者",页面报错或者异常,那么就是字符型,而且一般来说输入谁谁报错,那么闭合方式就是谁,如输入'报错,"正常,那么闭合方式就是',如果输入',"二者都报错,那么则考虑添加括号,如:'),")

3,直接在参数的值后面输入order by,判断字段数,如:http://www.test.com/item?id=1 order by 3

我们通常可以从order by 10开始尝试,若页面显示报错或异常,则order by 5,依次二分递减,直到显示正常与异常那个分界线,如:order by 3正常,order by 4异常,那么可以判定字段数为3,确定字段数,为之后的union select做准备

4,什么叫输出位置呢,以sqli中less-1为例,可以判断出,有图中箭头所指的两个输出位置

 

那什么叫没有输出位置呢,请看下图:

这里只会提示你一个you are in.......,根本没有输出位置,就算你的联合注入语句生效,可是它在前端没有输出位置,显示不出来,我们拿不到数据,又有什么用呢。

5,有输出位置情况下,常规来看,我们还是无法输出,它始终输出查询到的id=1的信息,但是如果我们将id的值赋值为-1或者999999,一个数据库根本查不到的值,那么这两个输出就空出来了,我们可以通过union select直接注入,如sqli中的less-1我们判断出为字符型 ' 闭合 ,也通过order by 判断了其字段数为3:

?id=-1'union select version(),database()--+

此处--+与#效果相同皆为注释,也可以写为%23,#的url编码就为%23

可以看到我们直接爆出了它的mysql版本,以及数据库名

如何通过联合注入爆表名,表字段名,爆数据呢?

这就要说到mysql5.0版本之后,会生成一个虚拟数据库,名为information_schema,里面存在所有数据库的库名,表名,字段名。

你只需要了解,有一个information_schema的数据库,里面有两张表,tables表,有两个字段存着所有的数据库名(table_schema)和表名(table_name),columns表有一个字段存着所有的字段名(column_name)。

以sqli中的less-1为例:

  • 爆表名:
?id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema='security')--+

或者将上面的 'security' 改为database()也行,效果如下:

此处用到了group_concat函数,可将字符串连接,还可添加输出格式,作用是将多行的查询结果显示到一行,否则将报以下错误:

  • 爆字段:

?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+

  • 爆数据:

?id=-1' union select 1,2,group_concat(id,0x3a,username,0x3a,password,0x7e) from security.users--+

0x7e是~号

0x3a是冒号:

爆出数据如下:

1.3报错注入

前提,前端有mysql的报错提示,则可进行报错注入。

思路:

  • 没有输出位置,构造 '或"有报错信息,可以选择报错注入(类似以下的报错信息)

报错注入常用的两个函数:extractvalue(),updatexml()

先来看看它们的参数:

  • extractvalue(目标xml文档,xml路径)

  • updatexml(目标xml文档,xml路径,更新的内容)

目标xml文档这个参数你不需要理解它是个什么东西,这个参数就是填字符串的,随便填个1,或者'a'都可以,然后函数会去你输入的xml路径查找存不存在。

这里关键的是第二个参数,xml路径,既然是路径那么自然是/开头,如/test/a/b这种,而#test这种显然不是一个路径的格式,而这两个函数当xml参数为路径时,无论找到与否都不会报错的,但是当xml路径不是一个路径写为#,%,&之类,那么就会报错,我们则可以利用这个报错,进行注入输出我们想要得到的数据。

以sqli-less-5为例:

?id=1' extractvalue('a',concat('~',(select database())))--+

concat()是字符串连接函数,输出如下:

剩下的爆表爆库名也跟联合注入差不多了,换汤不换药,利用information_schema这个数据库:

  • 爆表名

?id=1'and extractvalue(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema='security' )))#
  • 爆字段:

?id=1'and extractvalue(1,concat('~',(select group_concat(column_name) from information_schema.columns where table_schema='security'and table_name='users')))#
  • 爆数据

?id=1'and extractvalue(1,concat('~',(select group_concat_ws(':',id,username,password,'~') from security.users)))#

 

1.4布尔盲注

适用条件:

  • 网页屏蔽了报错

  • 没有输出位置

  • 构造' 或"虽然没有报错,但是页面显示会异常

以sqli-less-8为例:

?id=1 正常:

?id=1' 异常:

(%27是'的url编码)

像这种情况我们就可以考虑布尔盲注。

思路:

  • 1.判断注入点

  • 2.判断数据库长度(判断闭合方式)

  • 3.爆数据

以sqli-less-8为例(判断闭合方式为'),判断数据库长度:

'and length(database())>=n--+

通过n不断的增加,判断数据库的长度,

如'and length(database())>=9--+,页面异常

'and length(database())>=8--+,页面正常,那么可以判定,数据库名的长度的为8

爆库名:

'and substr(database(),1,1)='a'--+

substr()截取字符串的函数,第一个参数是被截取的字符串,第二个是截取多少个字符串,第三个是从第几位开始截取

当尝试到'and substr(database(),1,1)='s'--+,我们发现页面终于正常,便确定数据库名第一个字母为s

如此尝试工作量太大, 我们便使用工具burpsuite爆破。

1.4.1burp的简单使用

 

首先推荐使用火狐浏览器,火狐浏览器必装插件:foxyproxy,wappalyzer,shodan

foxyproxy方便快速开启代理,便于我们使用burp爆破,安装foxyproxy后,浏览器右上角有个小狐狸,点击选项可进行添加代理:

 

 

代理IP写127.0.0.1,也就是本机服务器,填写的端口自己选定,但是要与burp的代理设置的端口一致,如下图:

如果此时burp抓不了包,那么重启burp试试。

设置完代理后,我们以后只需点击右上角小狐狸,进行快速开启或关闭代理。

开启代理后,我们开启burp拦截请求,如下图:

浏览器进行爆库名请求,burp抓包如下:

我们看到第一行的GET方法,有我们的注入语句,右键选择intruder,

点击测试器,点击位置,攻击类型选择集束炸弹(关于这四种攻击类型,此处不做详讲,请读者自行百度),点击§清除(默认勾选了所有的参数为有效载荷,然而我们不需要那么多),再选中我们想要进行爆破的参数添加§,效果如下:

再选择有效载荷,载荷类型不改,选择从列表添加(下滑有0~9的选项),载荷集1是数据库名截取字符串的位置,数据库名长度前面我们判断为8(删除0,9即可),如下图:

再选择有效载荷集2,这个地方是爆数据库名的某一位具体为哪个字母的,从列表添加,a-z,由于我们是上帝视角就不加数字,下划线阿那些了,条目太多跑起来时间有点久

最后选择选项,只需线程数修改为30~40即可,太大不好,cpu遭不住,太小,跑到猴年马月,其他设置默认便好,如下图:

最后点击右上角,开始攻击,攻击结果如下:

点击长,进行排序,可以看到它们的长是不一样的(前面8个都是910,后面的都是926),排在前面的按照顺序拼出来正是数据库名security

经验分享:

  • 爆破数据库名,可根据网站名来猜测,如果网站名为59同城,那我们或许可以猜测它的数据库名前两位为59,如果为地名balabala,那有可能数据库名前几位就是地名的拼音缩写

  • 如果有的时候排序后,缺少第某一位的结果,那可能是我们输入的字典不够强大,a-z,0-9,A-Z,特殊符号等等,可以在该攻击结果界面点击有效载荷,将载荷集修改为针对某一位的爆破,如缺少第4位的结果,我将有效载荷集1,只需添加一个4即可,再在有效载荷集添加更大一点的字典如a-z,A-Z,0-9,_,-,+,=,_等添加进去,但一般来说,如果字典为字母,数字都爆不出来,一般来说都是_,最后再在攻击那个地方选择重即可,如下图:

burp的使用还有一个repeater,重定向,此处不细讲,读者可自行探索。

爆表名:

'and substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1)='a'--+

limit 0,1 选定输出多少个结果,此处为查询到的第一个表,0,从0开始计数,1,从0开始的第一个

limit 1,2 表示输出查询到的结果的从第二位起的两个条目

写的时候,我们可以先把大体结构写好,不容易出错,如:' and substr((),1,1)='a'--+,先把函数写出来,我们是截取一个字符的,在后面先把字符写好(随便写一个a),然后再在substr()函数里添加三个参数,第一个参数是我们的sql查询语句有点长,我们可以先括号括起来不写,把后面两个参数1,1写了,然后再来先前的括号里写sql语句

爆字段:

'and substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),1,1)='a'--+

报数据:

'and substr((select (id,username,password) from security.users limit 0,1),1,1)='a'--+

如果你觉得爆破生成的条目太多,可以选择一点一点的爆。

1.5时间盲注

条件:在布尔盲注的基础上,输入'或者"页面依然正常或者说没有任何反馈。

也是需要配合burp爆破,需要用到的函数有:

  • substr()

  • if(exp1,exp2,exp3)

  • sleep(n)

substr()前面我们讲过,此处不再赘述。

if()函数有三个参数,exp1为真时执行exp2,否则执行exp3

sleep()函数,则是睡眠,如sleep(5),睡眠五秒,本该点击立即有反应的页面会转圈转几秒,会有明显的延迟。

以sqli-less-9为例(什么时候都不要忘了确定闭合方式):

爆数据库名:

'and if(substr(database(),1,1)='s',sleep(5),1)--+

如果第一个表达式substr(database()='s'为真,则执行sleep(5)睡眠五秒,页面会有明显延迟,否则页面会有立即的响应。

同样时间盲注也需要配合burp提高我们的爆破效率。

爆字段名:

'and if(substr((select column_name from information_schema.columns where table_schema='security'and table_name='emails' limit 0,1),1,1)='i',sleep(5),1)--+

报数据:

'and if(substr((select concat(id,0x3a,email_id) from security.emails limit 0,1),1,1)='1',sleep(5),1)--+

1.6堆叠注入

简介:使用条件有限,但是威胁较大,原理是没有过滤;分号,使得能执行多条sql语句函数,没有预编译,虽然前端不显示,但是可以通过时间盲注得到信息。

以sqli-less-38为例:

1';create database abc;--+
1';use abc;create table abc(id int(4) primary key not null,name varchar(10));
1';insert into abc.abc values(1,'a'),(2,'b'),(3,'c');

分步执行以上三条sql语句,打开navicat你能看到你所建的数据库,表,列,值

还可以进行删库,修改表等操作。

但由于使用条件有限,此处不过多讲解。

1.7宽字节注入

有的时候开发人员有一定的安全意识,会加入slash()函数,你的输入的非法字符'会被转义,成为\',从而你的注入失效。

那么如何绕过呢?

首先我们来了解mysql字符转换的三个步骤:

  • 收到请求

  • 内部操作

  • 结果输出

宽字节注入原理:

当满足以下条件时,双字节会被解析为一个汉字,从而实现绕过:

  • ascii值范围为128~254 + 64~254构成一个汉字(两个字节范围)

%df ,%dc ,%d5c都可以实现逃逸

如%df'

如前面的联合注入:

%df' union select 1,2,database()--+

 

1.8cookie注入

在已经登陆的情况下,服务器会生成一个cookie,是你登陆状态的凭证。

通过burp抓包,能看到请求里面有cookie这一行,可以尝试在cookie末尾构成'或",通过右键选择repeater重定向,看反馈的页面是什么样的。

以sqli-less-20为例,首先admin,admin登陆一下,退出,再在登陆是burp抓包进行cookie注入,一般在遇到真实网站时,可以找到注册页面先注册一个账户再进行cookie注入。

以下为登陆状态:

下面我们进行重新登陆,并抓包进行cookie注入:

我们可以看到cookie,有这么一个参数和值,那么我们就在此处像在get方法里面对待参数和值那样进行尝试注入(换汤不换药,只是换了个地方)。

2.XSS

又称之为跨站脚本攻击,有三种类型:

  • 反射型

  • 存储型

  • DOM型

原理:没有过滤非法输入,使得用户的非法输入被解析为网页js代码,实现恶意js 的功能。

反射型就是url后面可以跟上能够被执行的恶意js代码,然后诱导别人点击你的跨站脚本(可以利用站长之家生成短链接让受害者放松警惕),受害者点击了你的脚本之后,你可以获取对方的cookie,然后利用对方的账户对服务器发起攻击。

存储型,一般出现在能够把恶意脚本存储到服务器的地方,如评论区,个人信息填写的地方之类。

DOM型,只与前端发生交互,不与后台产生交互。

3.CSRF

也叫跨站请求伪造,是劫持受信任用户,通过窃取cookie伪装用户身份,对服务器发起“合法”请求的攻击。

修复建议:

  • 通过token或者session来判断当前用户身份(黑客获得了用户的cookie相当于获得了小区的通行证,没做token和session验证相当于你获得了这张通行证你就是那个小区里的人的身份,可以随意进出,但是有了token和session验证就相当于你虽然有了通行证但还要说出一个口号,这个口号是随机生成的,对不上,保安不让你进去)

  • 敏感操作需要验证码,更改密码操作需要验证旧密码。

4.SSRF

服务端请求伪造,由攻击者构造形成由服务端发起请求的安全漏洞,一般通过服务端访问内网的资源,服务端此时是充当一个中间人的作用,也是我们搜集其内网信息的一个跳板。

有兴趣的读者可以去了解了解file_get_content()这个函数。

5.RCE

远程命令执行漏洞,一般在一些网站提供特殊功能如ping,文件备份等,但是开发者并没有对用户输入的非法信息做过滤,使得可以直接相当于在操控服务器的cmd命令,可以进行提权,进而拿下服务器。

未完:写在后面的话

只是简单做了一些漏洞介绍,之后再出个练习专篇。

锻炼思路,坚持练习,就像刷数学题一样,概念你学了,定理公式你学了,最终还是要去刷题的,解题就要涉及到一个解题思路,只要不断练习,总结,到真正需要我们解题的时候,才不至于太无动于衷。

天道酬勤,你会变成大神的。

posted @ 2020-08-25 20:31  nihinumbra  阅读(540)  评论(0编辑  收藏  举报