一、有关html/css, js, php, cgi 的一些认识
selector 常见的就 type, class(.), id(#),注意的是 id 只能一个页面一个。
Remember, HTML will define the content and structure of our web pages, while CSS will define the visual style and appearance of our web pages.
当我们浏览器访问一个站点的静态文件,会把文件内容都下载下来(一般压缩),当然如果遇到外联的css/js,会再发起请求得
到。如果我们右键查看网页源代码,一片混乱没法看,可以使用firefox + firebug,可以清晰看到html dom tree,右键inspect
element 可以很快定位到tree node,由于是下载到本地,所以可以自己尝试修改element 查看效果,这并不影响服务器上的原始
文件。最后浏览器会开始渲染,包括执行js比如document.write() 之类,就呈现出现在我们所看到的网页模样,可以使用firefox F12 断点调试js。
所谓的 dom 树操作就是一系列类似 getElementById 之类的函数。注意 js 是在客户端执行的,可以动态地改变 dom 树,通俗地说就是可以改变页面
的html,人们从浏览器看见的页面也就变化了。
html 的解析顺序:html parser --> css parser -->javascript parser
浏览器解码顺序:html decoding --> url decoding --> javascript decoding
CGI 的意思是啥?不是一种语言,也不是一种技术,而是一种模式。搜索一下CGI的定义Common Gateway Interface,简称CGI。在物理上是一段程序,存放在服务器上。只要是提供数据输出的服务器端程序都可以叫CGI,ASP/PHP/JSP这些都可以认为是,你用C/C++写一个可以提供数据输出的服务器端bin文件,也叫CGI,至于python/perl/shell 等脚本当然也能写cgi。
对一个 CGI 程序,做的工作其实只有:从环境变量(environment variables)和标准输入(standard input)中读取数据、处理数据、
向标准输出(standard output)输出数据。环境变量中存储的叫 Request Meta-Variables,也就是诸如 QUERY_STRING、
PATH_INFO 之类的东西,这些是由 Web Server 通过环境变量传递给 CGI 程序的,CGI 程序也是从环境变量中读取的。
标准输入中存放的往往是用户通过GET 或者 POST 提交的数据,这些数据也是由 Web Server 传过来的(客户端提交)。传统的
get 即是以 url?key1=value1&key2=value2的 形式传输过去。而post 形式(http请求包体)就比较多了,可以是传统的
key=value,也可以是json/xml 等形式,只是这些从标准输入得到后还需要经过一个解析的过程才能得到想要的key=value 形式的呈现。
注意标准输入的概念,如果在本地执行 php xx.php args 那些 xx.php 的标准输入就是控制命令窗口,获取输入需要通过 $argv;如果是通过 uri 路径访问 xx.php 如 http://localhost/xx.php,那么 xx.php 的标准输入来自 webserver 给的数据,可以通过 php://input 获取。
当然cgi 的body输出也是多种形式了,可以是简单的application/json、text/xml 形式,也可以是php echo 出一个text/plain or text/html,但要明确的是php 等脚本是在服务器端执行的,也就是说当客户端访问test.php 时,server 先执行php脚本(php 会 读取标准输入,处理过程,向标准输出输出数据),形象地来说,就是“戳一次就动一次”,根据用户输入的不同而产生不同的输出结果,即动态网页的概念。注意,php, js css, 都可以和html 标签写在同个html 文件中。
有时候访问出现403 forbidden ,有种原因是 apache 设置的user,即运行httpd的user 是nobody(假设),对你想要访问的目
录/文件 没有读或者执行的权限,所以server 没办法读取执行文件,故 禁止访问。还有种情况是配置文件写明 deny xxx,禁止某些来源ip 访问。
二. 各种编码、转义相关
从浏览器 url发出的请求,如果进行了 urlencode(比如chrome一般会 urlencode 而 ie 不会,一般copy而来的url会被encode),比如将 " 转
成%22 发出去,在服务器端的php 接收到的是原始的" 还是编码后的%22 得看用$_GET["key"] 还是$_SERVER['QUERY_STRING'],还要看在php 脚
本内有没有做addslashes 或者 htmlspecialchars 等函数调用,这样就能判断解析脚本 echo/print 出来的html 是怎样的组织形式,当然客户端请求
得到的html 也就是这样的形式了。那为什么在chrome中对于< 等没有alert 弹窗呢,只是因为某些浏览器有anti_xss 模块或者filter,在浏览器解析
html 的时候 过滤掉这些危险的script 而没有执行。
htmlspecialchars 会把 < 编码成 < 还有 >,",' 等
addslashes() 函数在指定的预定义字符前添加反斜杠。这些预定义字符是:
单引号 (')
双引号 (")
反斜杠 (\)
NULL
这样就无法从url 传递带引号的参数来闭合引号来达到xss的目的,但是在charset=gbk 的情况下,如果参数含大于
127的值如%ae,后面再跟引号,虽然引号变成\', 但%ae\ 在gbk 看来也许是一个字符,当然我们看起来好像是一个
乱码,这样也会造成引号可以闭合,sql 注入也存在这样的字符集解析问题。设置了某种字符集,浏览器会按这种编
码去解析html 来展示给用户。(memchr)
虽然大部分浏览器不会解析 Content-type: application/plain 响应头下的 body,但如 safari 以及某些版本的ie 都可能不按套路行事。在一些不需要html 解释的页面,可以通过设置Content-type:application/json 来避免 xss。
反斜杠 \ 在script域内会起转义作用,而在html 标签内就是表示的字符含义,从下面alert()出来的字符可以得知。
<script> var test="a\""; alert(test); </script>
<script> var test='a\''; alert(test); </script>
<script> var test='a'; alert(test); </script>
<input type="text" value="a\" onmousemove=alert(/xss/) " />
三. 登录跳转、登录验证
一般网站登录前的验证可能是这样实现的:
<form action="processs.php", id="login" method="post" onsubmit="return validate();">
在用户填完信息后会先调用validate() 函数进行验证,如果返回true 才会真正提交表单。
在validate() 里类似 if(document.forms.login.agreement.value != checked) { return false;}
// document.getElementById("login").agreement.value id 必须全局唯一
在不想重载页面,也就是不提交,可以 onsubmit="quote(); return false;" 在quote()里面可以 xhr= new XMLHttpRequest();
即ajax的方式来做一些操作。
一般的网站登录跳转实现方式之一是:在login.php 对表单post 过来的user&pwd&email 验证(见下面),如果对则设置一个键值如 $_SESSION["auth"]=true,设置response 的Location Head : home.php,本程序exit。浏览器接收到rsp,看到Location 头部,于是跳转请求至home.php。home.php 可以对$_SESSION["auth"] 继续判断一次,若true 则显示登录后的页面。当然这一切的前提是login.php开启了session_start(),这样第二次访问home.php 也会带上Cookie:PHPSESSID=xxx ,这样server 通过 $_COOKIE 获取sessionId就知道是同个用户的请求,通过sessionId 就可以知道 $_SESSION 结构体中原本存放的数据,比如auth=True 之类。
superglobals : $_COOKIE $_ENV $_FILES $_GET $_POST $_REQUEST $_SERVER $_SESSION
实际上带登录态的漏洞扫描也是带上cookie 实现的,需要注意cookie失效的问题。一般server对每个网页请求会做登录验证,常用两种方式:
1). 从cookie头中获取sessionId,进而从server 端存储的Session信息中获取相关验证信息,如user&pwd&email之类,与post过来的信息进行比对(可能需要根据post数据字段查数据库),验证通过则显示请求的网页,否则跳转到登录页面。
Session 的优点是简单易用,可以直接从Session中取出用户登录信息。Session 的缺点是服务器需要在内存中维护一个映射表来存储用户登录信息,如果有两台以上服务器就需要对Session 做集群,因此使用Session的Web App 很难扩展。
比如 web.py 框架中 session.py 有:
'cookie_name': 'webpy_session_id' // 定义
self.session_id = web.cookies().get(cookie_name) // server 获取sessionId
self._setcookie(self.session_id) // server 设置Client 的cookie,Set-Cookie 头,即调用下面的函数
web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
self.store[self.session_id] = dict(self._data) // server 端存储session_id 相关的用户数据
2). 先直接根据post 过来的数据字段查数据库进行比对,但此时还需要验证cookie 不是伪造的,实现防伪造cookie的关键是通过一个单向算法
(例如MD5),举例如下:
当用户输入了正确的口令登录成功后,服务器可以根据post数据从数据库取到用户的id,并按照如下方式计算出一个字符串:
"用户id" - "过期时间" - MD5("用户id" + "用户口令" + "过期时间" + "SecretKey")
并通过set-cookie 设置浏览器的cookie。
当浏览器发送cookie 到服务器端后,服务器可以拿到的信息包括:用户id、过期时间、MD5值
如果未到过期时间,服务器就根据用户id 查找用户口令,并计算:
MD5("用户id" + "用户口令" + "过期时间" + "SecretKey")
并与浏览器cookie中的MD5 进行比较,如果相等则说明用户已登录,否则cookie就是伪造的。
这个算法的关键在于MD5是一种单向算法,即可以通过原始字符串计算出MD5,但无法通过MD5反推出原始字符串。
四、session, token, cookie的区别与联系
1. 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识sessionId。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。通常会话Session随着会话cookie删除而删除,而持久型Session信息会永久保存,虽然会随着持久型cookie的失效而无用。
2. Session cookie
A session cookie, also known as an in-memory cookie or transient cookie, exists only in temporary memory while the user navigates the website.When an expiry date or validity interval is not set at cookie creation time, a session cookie is created. Web browsers normally delete session cookies when the user closes the browser.
思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息(如 Cookie: PHPSESSID=xxxxx;)到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID(Examples of the names that some programming languages use when naming their cookie include JSESSIONID (JEE), PHPSESSID (PHP), and ASPSESSIONID (Microsoft ASP).),以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
会话cookie: 是一种临时的cookie,它记录了用户访问站点时的设置和偏好,关闭浏览器,会话cookie就被删除了。
3.Persistent cookie
A persistent cookie outlasts user sessions. If a persistent cookie has its Max-Age set to one year (for example), then, during that year, the initial value set in that cookie would be sent back to the server every time the user visited the server. This could be used to record a vital piece of information such as how the user initially came to this website. For this reason, persistent cookies are also called tracking cookies.
持久型cookie 一般用来保存一些少量信息,如当初用户是从哪个url 跳转来的。
持久cookie: 存储在硬盘上,(不管浏览器退出,或者电脑重启,持久cookie都存在), 持久cookie有过期时间。
所以,总结一下:
Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
client :
Cookie: name=value; name2=value2
server:
Set-Cookie: LSID=DQAAAK…Eaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
Set-Cookie: HSID=AYQEVn….DKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly
Set-Cookie: SSID=Ap4P….GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
Domain 和 Path 决定浏览器在访问此站点某目录下的网页时cookie 才会被发送出去。Expires 确定cookie的过期时间,没有过期时间则为session
cookie,有则是persistent cookie。Secure 表示只有通过https 连接才会发送cookie。HttpOnly 表示只有通过http 访问才会发送cookie,比如在客户端
执行js: document.cookie 是获取不到cookie的。
桌面应用程序也通过HTTP协议跟Web服务器交互, 桌面应用程序一般不会使用cookie, 而是把 "用户名+冒号+密码"用BASE64 编码的字符串放在http request 中的header Authorization 中发送给服务端, 这种方式叫HTTP基本认证(Basic Authentication)
在chrome浏览器里可以用 http://username:password@url这种方式直接略过这个基本认证