在上一章中,我们了解了 web 应用安全过程以及测试应用安全性的重要性。在本章中,我们将了解以下主题:
- HTTP 协议基础
- HTTP 请求的剖析
- 使用请求库与 web 应用交互
- 分析 HTTP 响应
在本节中,我们将了解 HTTP 协议,它是如何工作的,它的安全方面,以及在执行请求时支持哪些方法。
这将为您提供 HTTP 的基本知识,这对于理解如何构建工具和测试 web 应用中的安全问题非常重要。
HTTP 被设计为支持客户端和服务器之间的通信。
HTTP 是在应用层运行的基于 TCP/IP 的通信协议。通常,我们使用 web 浏览器与 web 应用交互,但在本次培训中,我们将不使用浏览器,而是使用 Python 与 web 应用进行对话。此协议与媒体无关。
这意味着只要客户机和服务器知道如何处理数据内容,就可以通过 HTTP 发送任何类型的数据。而且它是无状态的,这意味着 HTTP 服务器和客户端仅在请求事务的过程中相互了解。由于此特性,客户端或服务器都不会在请求之间保留信息,这将在以后执行某些攻击时有所帮助。
HTTP 协议有两种不同版本:
- HTTP/1.0:这将为每个请求/响应事务使用一个新连接
- HTTP/1.1:这是一个或多个请求-响应事务可以使用连接的地方
HTTP 不是一种安全协议,这意味着所有通信都是明文,容易被拦截和篡改。
通常,HTTP 正在端口80
上服务。下面是一个简单事务的示例:
在左边,我们有一个客户端,它向服务器发送一个 HTTPGET
请求,请求资源test.html
。服务器返回一个 HTTP 响应,其中包含一个200 OK
代码、一些头和内容test.html
(如果它存在于服务器上)。
如果不存在,则返回404 Not Found
响应码。这代表了 web 应用世界中最基本的GET
请求。
1994 年,HTTPS 被引入,在 HTTP 之上增加了安全性。HTTPS 本身不是协议,而是在安全套接字层(SSL)或传输层安全(TLS之上分层 HTTP 的结果。
HTTPS 在不安全的网络上创建安全通道。这确保了对窃听者和中间人攻击的合理保护,前提是使用了足够的密码套件,并且服务证书经过验证和信任。因此,每当应用处理敏感信息时,如银行付款、购物网站、登录页面和个人资料页面,都应该使用 HTTPS。基本上,如果我们处理流程或存储客户数据,它应该使用 HTTPS。
在 HTTP 中,方法表示要对所选资源执行的所需操作,也称为 HTTP 谓词。HTTP/1.0 定义了三种方法:
HEAD
:只返回表头和状态码,不返回其内容GET
:这是用于检索给定 URI 的资源内容的标准方法POST
:这是一种向服务器提交内容、表单数据、文件等的方法
然后,HTTP/1.1 引入了以下方法:
OPTIONS
:提供目标资源的通信选项PUT
:请求存储给定 URI 标识的资源DELETE
:这将删除给定 URI 标识的目标资源的所有表示TRACE
:此方法回显接收到的请求,以便客户端可以看到中间服务器进行了哪些更改或版本CONNECT
:这将建立到 HTTPS 使用的给定 URI 标识的服务器的隧道PATCH
:此方法对资源应用部分修改
HEAD
、GET
、OPTIONS
和TRACE
按照惯例被定义为安全的,这意味着它们仅用于信息检索,不应改变服务器的状态。
另一方面,诸如POST
、PUT
、DELETE
和PATCH
等方法旨在用于可能对服务器或外部产生副作用的操作。还有比这些更多的方法。我鼓励你去探索它们。
我们已经看到 HTTP 是一种无状态的客户机-服务器协议。
此协议不提供任何安全性,因此创建 HTTPS 是为了在 HTTP 之上添加一个安全层。我们还了解到,有一些不同的方法将指示服务器对所选资源执行不同的操作。
在本节中,我们将了解 URL 的结构、请求和响应头,以及一个使用 Telnet 的GET
请求示例,以了解它如何在较低级别上工作。
我打赌你现在已经看到了成千上万的网址。现在是时候停下来思考一下 URL 结构了。让我们看看每个部分的含义:
第一部分是 web 应用中的协议。使用的两个协议是 HTTP 和 HTTPS。使用 HTTP 时使用的端口为80
,使用 HTTPS 时使用的端口为443
。
下一部分是我们要联系的主机。接下来,我们可以看到该服务器中的资源或文件位置。在本例中,目录为content
,资源为section
。然后,我们有一个问号符号,它指示将出现的是查询字符串。这些参数将被传递到页面的部分以进行处理。
有一些替代方案,例如在主机之前添加用户名和密码进行身份验证,或者在 web 服务器未在标准的80
或443
端口中侦听的情况下显式定义端口。
现在,让我们谈谈标题。标头是 HTTP 请求和响应的核心部分。
它们描述了客户机和服务器之间的通信方式,并提供了有关事务的信息。我们有客户端头文件,由浏览器发送。一些例子如下:
- 用户代理:告知服务器用户有什么类型的操作系统、浏览器和插件。
- 接受编码:定义浏览器支持的编码,通常为 GZip 或 Deflate。这将压缩内容并减少每个事务的带宽时间。
- Referer:这包含 Referer URL,基本上是您在哪个页面单击该链接的。
- Cookie:如果我们的浏览器在其站点上有 Cookie,它会将它们添加到 Cookie 头中。我们也有服务器端头,由 web 服务器设置。
- 缓存控制:定义了链上所有缓存机制必须遵守的指令。
- 位置:用于重新定向。无论何时有
301
或302
响应,服务器都必须发送此头。 - 设置 Cookie:用于在用户浏览器中设置 Cookie 的表头。
- WWW-Authenticate:服务器使用此头请求认证。当浏览器看到此标题时,它将打开一个登录窗口,询问用户名和密码。
这是向发出GET
请求时响应头的一个示例 https://www.packtpub.com/ :
我们在这里提到了其中的一些,例如cache-control
、content-encoding
和content-type
。我建议你把它们都熟悉一下。每次你找到一个新的标题,阅读它来了解它的功能。
在查看 URL 结构和标题之后,让我们在真正的服务器上尝试一个GET
请求。
为此,我将使用终端和telnet
命令向服务器发送原始GET
请求。这是我们通过键入 Telnet 连接来模拟浏览器的尝试。
执行以下步骤:
- 让我们切换到虚拟机,打开终端并键入以下内容:
telnet www.httpbin.org 80
80
is the port we want Telnet to connect to. httpbin.org
is a website that provides an HTTP request and response service that is useful to test tools.
点击进入。
- 连接后,我们将看到以下消息:
这意味着连接已建立。
- 接下来,我们输入
GET /ip HTTP/1.0
并点击回车两次。这是我们告诉服务器我们正在使用GET
请求名为/ip
的资源。然后,我们指定HTTP/1.0
协议,然后按Enter两次。因此,我们从服务器获得第一个响应:
请注意,我们在请求中根本没有使用任何头,但是我们从服务器收到了许多头,以及资源 IP 的内容。
在这种情况下,内容是发出请求的机器的 IP 地址。
现在,让我们再举一个例子,但这次请求一个包含参数的 URL。
打开终端并键入:
telnet www.httpbin.org 80
GET /redirect-to?url=http://www.bing.com HTTP/1.0
同样,我们使用了GET
,但这次我们请求资源重定向到,查询字符串中的参数 URL 值为http://www.bing.com :
在这种情况下,服务器基本上将浏览器重定向到提供的 URL,使用位置头并返回一个302
重定向代码。在这种情况下,不会发生任何事情,因为 Telnet 不会解释该报头。记住,这是一个规则连接。
在本节中,我们将开始编写 Python 代码,以使用请求库执行 HTTP 请求。
Requests 是用 Python 编写的 Apache2 许可 HTTP 库。创建它是为了减少使用urllib2
和目前可用的其他 HTTP 库时所需的复杂性和工作量。
这是使用urllib2
库时使用身份验证执行api.github.com
请求所需的代码示例:
import urllib2
gh_url = 'https://api.github.com'
req = urllib2.Request(gh_url)
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, gh_url, 'user', 'pass')
auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_manager)
urllib2.install_opener(opener)
handler = urllib2.urlopen(req)
print handler.getcode()
print handler.headers.getheader('content-type')
这是相同的功能,但使用requests
库:
import requests
r = requests.get('https://api.github.com', auth=('user', 'pass'))
print r.status_code
print r.headers['content-type']
简单性是显而易见的。在编写脚本时,它确实方便了我们的工作。
让我们开始用 Python 编程。在第一个示例中,我们将使用 Python 和requests
库执行GET
请求:
- 让我们在虚拟机中打开 Atom 编辑器,通过导航到 file | new file 创建一个新文件。
- 首先,我们将导入
requests
库。这可以通过键入import requests
来完成。 - 现在,我们需要创建一个变量 R,在这里我们将用
GET
方法实例化一个 requests 对象,本例中的目标 URL 是httpbin.org/ip
:
import requests
r=requests.get('http://httpbin.org/ip')
- 最后,我们使用
print r.text
打印响应的内容。 - 将
/Examples/Section-2
文件夹中的文件另存为Chapter-3.py
。 - 让我们在终端上运行它。打开终端,使用以下命令将目录更改为
/Example/Section-2
:
cd Desktop/Examples/Section-2/
- 接下来,我们使用以下命令运行它:
python Chapter-3.py
我们可以看到响应体,在这里我们可以再次看到我的 IP:
记住,/ip
在主体中返回调用方 IP。
这是我们使用requests
库的第一个脚本。祝贺您,您正在使用 Python 与 web 应用通信!
现在,让我们在GET
请求中添加一个查询字符串:
- 为了做到这一点,我们将添加一个名为payload的变量和一个字典,其中每个键都是参数名,值将是该参数的值。在这种情况下,参数为 URL,值为
http://www.edge-security.com
。 - 然后,我们将资源更改为
/redirect-to
而不是 IP。此资源希望参数 URL 具有有效 URL,这将重定向我们。 - 我们还需要在请求
params=payload
中添加有效负载作为params
的值:
import requests
payload= {'url':'http://www.edge-security.com'}
r=requests.get('http://httpbin.org/redirect-to',params=payload)
print r.text
- 然后,我们会保存它。
- 现在,如果我们运行脚本,我们将在
python Chapter-3.py
终端中看到重定向页面的内容。好了。
这里我们有终端中www.edge-security.com
的全部内容:
这就是我们向查询字符串添加参数的方式。
如果我们想查看服务器返回的代码,该怎么办?我们需要添加以下代码:
- 让我们通过键入
print "Status code:"
打印一些标题。 - 然后,我们可以使用以下命令打印一些格式:
print "t *" + str(r.status_code)
我们可以移除print r.text
以获得更清晰的响应。
- 我们将保存它,并使用 Python 和脚本名称在终端中运行它。结果我们可以看到状态
200
,表示请求有效:
现在我们将了解如何访问响应的标题。
- 我们将返回虚拟机中的编辑器并打开文件
Video-3-headers.py
,该文件已准备好保存一些键入内容。此脚本正在再次使用资源/IP。
为了访问响应头,我们使用请求对象的方法头。
为了逐行打印,我们可以做一个循环,从r.headers:
解包键和值。
- 让我们试着在终端上运行这个。
- 我们将使用 Python 和脚本文件名。您可以看到服务器返回的不同头、响应代码和响应正文内容。
如果我们只想请求报头以节省带宽并加快 reg 响应事务时间,该怎么办?我们回到编辑器,用head
方法更改get
方法。
我们保存脚本,然后移动到控制台并运行它。我们可以看到状态代码为200
,我们正在返回标题,但我们不再有响应正文内容:
这是因为使用的方法是head
,我们只从服务器获取头。
现在,我们将了解如何设置请求的头。
我们为什么要这么做?因为我们可能需要添加应用预期的自定义头。我们想伪造我们的用户代理,以欺骗服务器,使其认为我们是一个移动设备。我们可能想要更改post
头来欺骗服务器或负载平衡,或者我们可能想要强制或篡改头值,看看应用如何处理它。
让我们尝试设置一个标题:
- 返回编辑器中的脚本。我们将修改请求,将方法更改回
get
,并将资源从ip
更改为headers
。这将使http://bin.org
将其在响应正文中接收到的爬升标头发送给我们,以便进行调试:
#!/usr/bin/env
import requests
r = requests.get('http://httpbin.org/ip')
print r.url
print 'Status code:'
print '\t[-]' + str(r.status_code) + '\n'
print 'Server headers'
print '****************************************'
for x in r.headers:
print '\t' + x + ' : ' + r.headers[x]
print '****************************************\n'
print "Content:\n"
print r.text
- 保存它,然后运行它。
- 我们可以看到,用户代理,
requests
库随每个请求发送python-requests
。 - 现在,让我们回到编辑器,将
user-agent
头设置为随机测试值。我们需要添加一个名为myheaders
的字典,其中包含密钥名、用户代理和测试值Iphone 6
:
myheaders={'user-agent':'Iphone 6'}
- 我们还需要添加请求,一个名为 headers 的参数,其值为
myheaders
:
#!/usr/bin/env
import requests
myheaders={'user-agent':'Iphone 6'}
r = requests.post('http://httpbin.org/post',data={'name':'packt'})
print r.url
print 'Status code:'
print '\t[-]' + str(r.status_code) + '\n'
print 'Server headers'
print '****************************************'
for x in r.headers:
print '\t' + x + ' : ' + r.headers[x]
print '****************************************\n'
print "Content:\n"
print r.text
- 让我们在控制台中再次运行它。
我们可以看到服务器收到了我们修改过的用户代理,伪造了一个Iphone 6
:
现在,您知道如何操作标题了。
现在,我们看到了一个 TytT0 和一个 Ty1 T1 请求,让我们看一个 Outt2 请求,我们将发送表单参数:
- 返回 Atom 编辑器,用
post
替换get
方法。 - 我们还将更改 URL。这一次,我们将使用 post 资源
http://bin.org/post
并添加数据字典。
这通常是您在 web 应用中看到的表单数据。在本例中,我们在数据字典中添加一个参数,该参数具有键代码名和值packt
。我们保存它,然后在控制台中运行脚本。完美的我们可以在结果中看到,我们已经提交了带有值的字典表单。
祝贺您,您现在知道如何使用 Python 执行不同的 HTTP 请求了!
在本节中,我们将了解不同的 HTTP 响应状态代码和不同类别的 HTTP 响应代码。
然后,我们将编写示例以查看成功的响应或错误,最后,我们将看到重定向示例。
HTTP 协议定义了五类响应代码来指示请求的状态:
- 1XX 信息:100 个范围码用于信息目的。它只存在于 HTTP/1.1 中。
- 2XX 成功:200 个代码范围用于表示客户请求的操作已被接收、理解、接受和处理。最常见的是
200 OK
。 - 3XX 重定向:300 范围表示必须采取额外操作才能完成请求的客户端。这些代码大部分用于 URL 重定向。这组代码中最常见的是
302 Found
代码。 - 4XX 客户端错误:400 范围表示客户端有错误。最常见的是
404 Not Found
。 - 5XX 服务器端错误:范围 500 用于指示服务器端的错误。最常见的是
500 Internal Server Error
。
我们建议您在这里学习每组的不同代码: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
让我们编写一些代码。让我们在虚拟机中打开编辑器并创建一个新文件:
- 首先,我们输入
import requests
导入requests
库。 - 我们将为目标 URL 创建一个变量。我们将再次使用
httpbin.org
并键入:
url='http://httpbin.org/html'
- 然后,我们将打印带有
req.status _code
的响应代码。为此,我们输入以下内容:
req = requests.get(url)
- 打印
req.status_code
字符串的响应代码。这可以通过以下方式完成:
print "Response code: " + str(req.status_code)
- 就这样!我们将把
/Example/Section-2
中的文件保存为Video-4.py
并切换到终端运行脚本。 - 使用
python Video-4.py
。
您应该在响应中看到一个200
状态代码,这意味着我们的请求成功:
干得好,让我们继续。
让我们回到编辑器:
- 现在,让我们将目标 URL 更改为不存在的 URL。为了查看错误代码,我们将更改 URL 并写入
fail
:
import requests
url='http://httpbin.org/fail'
req = requests.get(url)
print "Response code: " + str(req.status_code)
- 让我们保存它并在终端中再次运行此脚本。
现在,当我们运行服务器时,它将返回一个404
状态码,这意味着在服务器上找不到资源:
因此,现在我们知道,我们可以向服务器请求目录和文件的列表,并找到哪些目录和文件存在,哪些不存在。很有趣,对吧?
现在,让我们看看如何处理重定向。我们将使用一个示例页面,该页面将获取一个参数 URL 并将我们重定向到该已定义的 URL:
- 让我们回到我们的脚本并修改它,以获得一个名为
payload
的新目录,其中将包含我们要重定向到的 URL。 - 我们将使用
payload='url'
重定向到www.bing.com
。我们可以这样做:
payload={'url':'http://www.bing.com'}
- 现在,我们将使用这个,资源重定向到并添加
params
参数,并将其设置为payload
。 - 最后,我们将使用
print req.text
打印内容:
import requests
url='http://httpbin.org/redirect-to'
payload = {'url':'http://www.bing.com'}
req = requests.get(url,params=payload)
print req.text
print "Response code: " + str(req.status_code)
- 我们将保存它并再次运行它。
我们现在得到了什么?A200
编码及内容 https://www.bing.com/ :
代码应该是302
,对吗?我们需要访问请求的历史记录以查看重定向。
- 让我们添加
print r.history
。历史记录是重定向链中所有响应的列表。我们将通过这个循环将 URL 和每个 URL 的响应代码打印到脚本中。 - 对于
x in req.history
,打印与 URL 连接的状态码:
import requests
url='http://httpbin.org/redirect-to'
payload = {'url':'http://www.bing.com'}
req = requests.get(url,params=payload)
print req.text
print "Response code: " + str(req.status_code)
for x in req.history:
print str(x.status_code) + ' : ' + x.url
- 保存并运行它:
现在我们可以看到,在200
之前,有一个302
重定向代码将我们的浏览器发送到www.bing.com。
在本章中,我们简要介绍了 HTTP,并看到了一个基本的GET
请求示例。我们还看到了可用于与 web 应用交互的不同 HTTP 方法。
我们还了解了 HTTP 请求。我们学习了如何使用 Python 和requests
库与 web 应用交互。我们进一步了解了 HTTP 请求剖析以及不同的 HTTP 方法和响应代码。
在第 3 章中,我们将学习如何编写 Web 爬虫程序,使用 Python 使用 Spider,以及如何使用 Scrasty 库。