作者:园长MM
JavaEE基础
JSP: 全名为java server page,其根本是一个简化的Servlet。 Servlet:Servlet是一种服务器端的Java应用程序,可以生成动态的Web页面。 JavaEE: JavaEE是J2EE新的名称。改名目的是让大家清楚J2EE只是Java企业应用。 什么叫Jsp什么叫Java我真的非常让大家搞清楚!拜托别一上来就来一句:“前几天我搞了一个jsp的服务器,可难吭了。”。 请大家分清楚什么是jsp什么是JavaEE! Java平台结构图:




















Tomcat 基础


Tomcat快速定位到网站目录:
如何快速的找到tomcat的安装路径:
-1、不管是谁都应该明白的是不管apache还是tomcat安装的路径都是随意的,所以找不到路径也是非常正常的。 -2、在你的/etc/httpd/conf/httpd.conf里面会有一个LoadModule jk_module配置用于集成tomcat然后找到JkWorkersFile也就是tomcat的配置,找到.properties的路径。httpd里面也有可能会配置路径如果没有找到那就去apache2\conf\extra\httpd-vhosts看下有没有配置域名绑定。 -3、在第二步的时候找到了properties配置文件并读取,找到workers.tomcat_home也就是tomcat的配置路径了。 -4、得到tomcat的路径你还没有成功,域名的具体配置是在conf下的server.xml。 -5、读取server.xml不出意外你就可以找到网站的目录了。 -6、如果第五步没有找到那么去webapps目录下ROOT瞧瞧默认不配置的话网站是部署在ROOT下的。 -7、这一点是附加的科普知识爱听则听:数据库如果启用的tomcat有可能会采用tomcat的数据源配置未见为conf下的context.xml、server.xml。如果网站有域名绑定那么你可以试下ping域名然后带上端口访问。有可能会出现tomcat的登录界面。tomcat默认是没有配置用户登录的,所以当tomcat-users.xml下没有相关的用户配置就别在这里浪费时间了。 -8、如果配置未找到那么到网站目录下的WEB-INF目录和其下的classes目录下找下对应的properties、xml(一般都是properties)。 -9、如果你够蛋疼可以读取WEB.XML下的classess内的源码。 -10、祝你好运。
apache快速定位到网站目录:
普通的域名绑定: 直接添加到confhttpd.conf、confextrahttpd-vhosts.conf
Resin快速定位到网站目录:
在resin的conf下的resin.conf(resin3.x)和resin.xml(resin4.x) Resin apache 负载均衡配置(从我以前的文章中节选的) APACHE RESIN 做负载均衡,Resin 用来做 JAVAWEB 的支持,APACHE 用于处理静态 和 PHP 请求,RESIN 的速度飞快,RESIN 和 apache 的配合应该是非常完美的吧。 域名解析: apache 的 httpd.conf:

二级域名绑定,需要修改:
E:\install\apache2\conf\extra\httpd-vhosts.conf
如:


APACHE 添加对 Resin 的支持:

LoadModule caucho_module "E:/install/resin-pro-3.1.12/win32/apache-2.2/mod_caucho. dll"
然后在末尾加上:
只有就能让 apache 找到 resin 了。 PHP 支持问题: resin 默认是支持 PHP 的测试 4.0.29 的时候就算你把 PHP 解析的 servlet 配置删了一样解析 PHP,无奈换成了 resin 3.1 在注释掉 PHP 的 servlet 配置就无压力了。


作者:园长MM
Request & Response(请求与响应)
请求和响应在Web开发当中没有语言之分不管是ASP、PHP、ASPX还是JAVAEE也好,Web服务的核心应该是一样的。 在我看来Web开发最为核心也是最为基础的东西就是Request和Response!我们的Web应用最终都是面向用户的,而请求和响应完成了客户端和服务器端的交互。 服务器的工作主要是围绕着客户端的请求与响应的。 如下图我们通过Tamper data拦截请求后可以从请求头中清晰的看到发出请求的客户端请求的地址为:localhost。 浏览器为FireFox,操作系统为Win7等信息,这些是客户端的请求行为,也就是Request。

host=localhost user-agent=Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/18.0 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 accept-encoding=gzip, deflate connection=keep-alive
请求头信息伪造XSS
关于伪造问题我是这样理解的:发送Http请求是客户端的主动行为,服务器端通过ServerSocket监听并按照Http协议去解析客户端的请求行为。 所以请求头当中的信息可能并不一定遵循标准Http协议。 用FireFox的Tamper Data和Moify Headers(FireFox扩展中心搜Headers和Tamper Data都能找到) 插件修改下就实现了,请先安装FireFox和Tamper Data:  点击Start Tamper 然后请求Web页面,会发现请求已经被Tamper Data拦截下来了。选择Tamper:


Enumeration e = request.getHeaderNames(); while (e.hasMoreElements()) { String name = (String) e.nextElement();//获取key String value = request.getHeader(name);//得到对应的值 out.println(name + "=" + value + "
");//输出如cookie=123 }






Java里面伪造Http请求头
代码就不贴了,在发送请求的时候设置setRequestProperty 就行了,如:
URL realUrl = new URL(url); URLConnection connection = realUrl.openConnection(); connection.setConnectTimeout(5000);//连接超时 connection.setReadTimeout(5000);// 读取超时 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); (………………………..)


Session
Session是存储于服务器内存当中的会话,我们知道Http是无状态协议,为了支持客户端与服务器之间的交互,我们就需要通过不同的技术为交互存储状态,而这些不同的技术就是Cookie和Session了。 设置一个session:session.setAttribute("name",name);//从请求中获取用户的name放到session当中 session.setAttribute("ip",request.getRemoteAddr());//获取用户请求Ip地址 out.println("Session 设置成功.");



Session 生命周期(从创建到销毁)
1、session的默认过期时间是30分钟,可修改的最大时间是1440分钟(1440除以60=24小时=1天)。 2、服务器重启或关闭Session失效。
注:浏览器关闭其实并不会让session失效!因为session是存储在服务器端内存当中的。客户端把浏览器关闭了服务器怎么可能知道?正确的解释或许应该是浏览器关闭后不会去记忆关闭前客户端和服务器端之间的session信息且服务器端没有将sessionId以Cookie的方式写入到客户端缓存当中,重新打开浏览器之后并不会带着关闭之前的sessionId去访问服务器URL,服务器从请求中得不到sessionId自然给人的感觉就是session不存在(自己理解的)。
当我们关闭服务器时Tomcat会在安装目录workCatalinalocalhost项目名目录下建立SESSIONS.ser文件。此文件就是Session在Tomcat停止的时候 持久化到硬盘中的文件. 所有当前访问的用户Session都存储到此文件中. Tomcat启动成功后.SESSIONS.ser 又会反序列化到内存中,所以启动成功后此文件就消失了. 所以正常情况下 从启Tomcat用户是不需要登录的. 注意有个前提,就是存储到Session里面的user对象所对应的User类必须要序列化才可以。(摘自:http://alone-knight.iteye.com/blog/1611112)
SessionId是神马?有什么用?
我们不妨来做一个偷取sessionId的实验: 首先访问:http://localhost/Test/SessionTest?action=setSession&name=selina 完成session的创建,如何建立就不解释了如上所述。 同时开启FireFox和Chrome浏览器设置两个Session: 



如何应对SessionFixation攻击
1、用户输入正确的凭据,系统验证用户并完成登录,并建立新的会话ID。 2、Session会话加Ip控制 3、加强程序员的防范意识:写出明显xss的程序员记过一次,写出隐晦的xss的程序员警告教育一次,连续查出存在3个及其以上xss的程序员理解解除劳动合同(哈哈,开玩笑了)。
Cookie
Cookie是以文件形式的凭证(精简下为了通俗易懂),cookie的生命周期主要在于服务器给设置的有效时间。如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。 这次我们以IE为例:

if(!"".equals(name)){ Cookie cookies = new Cookie("name",name);//把用户名放到cookie cookies.setMaxAge(60*60*60*12*30) ;//设置cookie的有效期 // c1.setDomain(".ahack.net");//设置有效的域 response.addCookie(cookies);//把Cookie保存到客户端 out.println("当前登录:"+name); }else { out.println("用户名不能为空!"); }
有些大牛级别的程序员直接把帐号密码明文存储到客户端的cookie里面去,不得不佩服其功力深厚啊。客户端直接记事本打开就能看到自己的帐号密码了。


javascript:document.write(document.cookie) 



HttpOnly
上面我们用javascript:document.write(document.cookie)
通过document对象能够拿到存储于客户端的cookie信息。 HttpOnly设置后再使用document.cookie去取cookie值就不行了。 通过添加HttpOnly以后会在原cookie后多出一个HttpOnly; 普通的cookie设置:
Cookie: jsessionid=AS348AF929FK219CKA9FK3B79870H;
Cookie: jsessionid=AS348AF929FK219CKA9FK3B79870H; 加上HttpOnly后的Cookie:
加上HttpOnly后的Cookie: Cookie: jsessionid=AS348AF929FK219CKA9FK3B79870H; HttpOnly;
(参考YearOfSecurityforJava) 在JAVAEE6的API里面已经有了直接设置HttpOnly的方法了: 


还可以设置下session有效期(30分):
CSRF (跨站域请求伪造)
CSRF(Cross Site Request Forgery, 跨站域请求伪造)用户请求伪造,以受害人的身份构造恶意请求。(经典解析参考:http://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/ )CSRF 攻击的对象
在讨论如何抵御 CSRF 之前,先要明确 CSRF 攻击的对象,也就是要保护的对象。从以上的例子可知,CSRF 攻击是黑客借助受害者的 cookie 骗取服务器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的内容。另外,对于服务器返回的结果,由于浏览器同源策略的限制,黑客也无法进行解析。因此,黑客无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。所以,我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行 CSRF 的保护。比如银行系统中转账的请求会直接改变账户的金额,会遭到 CSRF 攻击,需要保护。而查询余额是对金额的读取操作,不会改变数据,CSRF 攻击无法解析服务器返回的结果,无需保护。
Csrf攻击方式
对象:A:普通用户,B:攻击者
1、假设A已经登录过xxx.com并且取得了合法的session,假设用户中心地址为:http://xxx.com/ucenter/index.do 2、B想把A余额转到自己的账户上,但是B不知道A的密码,通过分析转账功能发现xxx.com网站存在CSRF攻击漏洞和XSS漏洞。 3、B通过构建转账链接的URL如:http://xxx.com/ucenter/index.do?action=transfer&money=100000 &toUser=(B的帐号),因为A已经登录了所以后端在验证身份信息的时候肯定能取得A的信息。B可以通过xss或在其他站点构建这样一个URL诱惑A去点击或触发Xss。一旦A用自己的合法身份去发送一个GET请求后A的100000元人民币就转到B账户去了。当然了在转账支付等操作时这种低级的安全问题一般都很少出现。
防御CSRF:
验证 HTTP Referer 字段 在请求地址中添加 token 并验证 在 HTTP 头中自定义属性并验证 加验证码 (copy防御CSRF毫无意义,参考上面给的IBM专题的URL)
最常见的做法是加token,Java里面典型的做法是用filter:https://code.google.com/p/csrf-filter/(链接由plt提供,源码上面的在:http://ahack.iteye.com/blog/1900708)
作者:园长MM
JDBC和ORM
JDBC:
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问。
JPA:
JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。是一个ORM规范。Hibernate是JPA的具体实现。但是Hibernate出现的时间早于JPA(因为Hibernate作者很狂,sun看不惯就叫他去制定JPA标准去了哈哈)。
ORM:
对象关系映射(ORM)目前有Hibernate、OpenJPA、TopLink、EclipseJPA等实现。
JDO:
JDO(Java Data Object )是Java对象持久化的新的规范,也是一个用于存取某种数据仓库中的对象的标准化API。没有听说过JDO没有关系,很多人应该知道PDO,ADO吧?概念一样。
关系:
JPA可以依靠JDBC对JDO进行对象持久化,而ORM只是JPA当中的一个规范,我们常见的Hibernate、Mybatis和TopLink什么的都是ORM的具体实现。 概念性的东西知道就行了,能记住最好。很多东西可能真的是会用,但是要是让你去定义或者去解释的时候发现会有些困难。 重点了解JDBC是个什么东西,知道Hibernate和Mybatis是ORM的具体的实现就够了。
Object:
在Java当中Object类(java.lang.object)是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。所以在认识Java之前应该有一个对象的概念。
关系型数据库和非关系型数据库:
数据库是按照数据结构来组织、存储和管理数据的仓库。 关系型数据库,是建立在关系模型基础上的数据库。关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织。当前主流的关系型数据库有Oracle、DB2、Microsoft SQL Server、Microsoft Access、MySQL等。 NoSQL,指的是非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
1、High performance - 对数据库高并发读写的需求。 2、Huge Storage - 对海量数据的高效率存储和访问的需求。 3、High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。
常见的非关系型数据库:Membase、MongoDB、Hypertable、Apache Cassandra、CouchDB等。 常见的NoSQL数据库端口:
MongoDB:27017、28017、27080 CouchDB:5984 Hbase:9000 Cassandra:9160 Neo4j:7474 Riak:8098
在引入这么多的概念之后我们今天的故事也就要开始了,概念性的东西后面慢慢来。引入这些东西不只仅仅是为了讲一个SQL注入,后面很多地方可能都会用到。 传统的JDBC大于要经过这么些步骤完成一次查询操作,java和数据库的交互操作:
准备JDBC驱动 加载驱动 获取连接 预编译SQL 执行SQL 处理结果集 依次释放连接
sun只是在JDBC当中定义了具体的接口,而JDBC接口的具体的实现是由数据库提供厂商去写具体的实现的, 比如说Connection对象,不同的数据库的实现方式是不同的。 使用传统的JDBC的项目已经越来越少了,曾经的model1和model2已经被MVC给代替了。如果用传统的JDBC写项目你不得不去管理你的数据连接、事物等。而用ORM框架一般程序员只用关心执行SQL和处理结果集就行了。比如Spring的JdbcTemplate、Hibernate的HibernateTemplate提供了一套对dao操作的模版,对JDBC进行了轻量级封装。开发人员只需配置好数据源和事物一般仅需要提供一个SQL、处理SQL执行后的结果就行了,其他的事情都交给框架去完成了。

经典的JDBC的Sql注入
Sql注入产生的直接原因是拼凑SQL,绝大多数程序员在做开发的时候并不会去关注SQL最终是怎么去运行的,更不会去关注SQL执行的安全性。因为时间紧,任务重完成业务需求就行了,谁还有时间去管你什么SQL注入什么?还不如喝喝茶,看看妹子。正是有了这种懒惰的程序员SQL注入一直没有消失,而这当中不乏一些大型厂商。有的人可能心中有防御Sql注入意识,但是在面对复杂业务的时候可能还是存在侥幸心理,最近还是被神奇路人甲给脱裤了。为了处理未知的SQL注入攻击,一些大厂商开始采用SQL防注入甚至是使用某些厂商的WAF。JDBCSqlInjectionTest.java类:
package org.javaweb.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class JDBCSqlInjectionTest { /** * sql注入测试 * @param id */ public static void sqlInjectionTest(String id){ String MYSQLDRIVER = "com.mysql.jdbc.Driver";//MYSQL驱动 //Mysql连接字符串 String MYSQLURL = "jdbc:mysql://localhost:3306/wooyun?user=root&password=caonimei&useUnicode=true&characterEncoding=utf8&autoReconnect=true"; String sql = "SELECT * from corps where id = "+id;//查询语句 try { Class.forName(MYSQLDRIVER);//加载MYSQL驱动 Connection conn = DriverManager.getConnection(MYSQLURL);//获取数据库连接 PreparedStatement pstt = conn.prepareStatement(sql); ResultSet rs = pstt.executeQuery(); System.out.println("SQL:"+sql);//打印SQL while(rs.next()){//结果遍历 System.out.println("ID:"+rs.getObject("id"));//ID System.out.println("厂商:"+rs.getObject("corps_name"));//输出厂商名称 System.out.println("主站"+rs.getObject("corps_url"));//厂商URL } rs.close();//关闭查询结果集 pstt.close();//关闭PreparedStatement conn.close();//关闭数据连接 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public static void main(String
args) { sqlInjectionTest("2 and 1=2 union select version(),user(),database(),5 ");//查询id为2的厂商 } }
现在有以下Mysql数据库结构(后面用到的数据库结构都是一样):








小结论:
通过控制台执行SQL注入可知SQL注入跟平台无关、跟开发语言关系也不大,而是跟数据库有关。 知道了拼SQL肯定是会造成SQL注入的,那么我们应该怎样去修复上面的代码去防止SQL注入呢?其实只要把参数经过预编译就能够有效的防止SQL注入了,我们已经依旧提交SQL注入语句会发现之前能够成功注入出数据库版本、用户名、数据库名的语句现在无法带入数据库查询了:

PreparedStatement实现防注入
SQL语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句。Class.forName(MYSQLDRIVER);//加载MYSQL驱动 Connection conn = DriverManager.getConnection(MYSQLURL);//获取数据库连接 String sql = "SELECT * from corps where id = ? ";//查询语句 PreparedStatement pstt = conn.prepareStatement(sql);//获取预编译的PreparedStatement对象 pstt.setObject(1, id);//使用预编译SQL ResultSet rs = pstt.executeQuery();

String sql = "SELECT * from corps where id = ? "; pstt = conn.prepareStatement(sql);//获取预编译的PreparedStatement对象 pstt.setObject(1, id);//使用预编译SQL ResultSet rs = pstt.executeQuery();
在通过conn.prepareStatement去获取一个PreparedStatement便会以预编译去处理查询SQL,而使用conn.createStatement得到的只是一个普通的Statement不会去预编译SQL语句,但Statement执行效率和速度都比prepareStatement要快前者是后者的父类。 从类加载到连接的关闭数据库厂商根据自己的数据库的特性实现了JDBC的接口。类加载完成之后才能够继续调用其他的方法去获取一个连接对象,然后才能过去执行SQL命令、返回查询结果集(ResultSet)。 Mysql的Driver:
public class Driver extends NonRegisteringDriver implements java.sql.Driver{}
在加载驱动处下断点(22行),可以跟踪到mysql的驱动连接数据库到获取连接的整个过程。





String sql = “select * from xxx where id = ”+id//这种必死无疑。
Web中绕过SQL防注入:
Java中的JSP里边有个特性直接request.getParameter("Parameter");去获取请求的数据是不分GET和POST的,而看过我第一期的同学应该还记得我们的Servlet一般都是两者合一的方式去处理的,而在SpringMVC里面如果不指定传入参数的方式默认是get和post都可以接受到。 SpringMvc如:
@RequestMapping(value="/index.aspx",method=RequestMethod.GET) public String index(HttpServletRequest request,HttpServletResponse response){ System.out.println("------------"); return "index"; }
上面默认只接收GET请求,而大多数时候是很少有人去制定请求的方式的。说这么多其实就是为了告诉大家我们可以通过POST方式去绕过普通的SQL防注入检测!
Web当中最容易出现SQL注入的地方
常见的文章显示、分类展示。 用户注册、用户登录处。 关键字搜索、文件下载处。 数据统计处(订单查询、上传下载统计等)经典的如select下拉框注入。 逻辑略复杂处(密码找回以及跟安全相关的)。
关于注入页面报错:
如果发现页面抛出异常,那么得从两个方面去看问题,传统的SQL注入在页面报错以后肯定没法直接从页面获取到数据信息。如果报错后SQL没有往下执行那么不管你提交什么SQL注入语句都是无效的,如果只是普通的错误可以根据错误信息进行参数修改之类继续SQL注入。 假设我们的id改为int类型:
int id = Integer.parseInt(request.getParameter("id"));

Spring中如何安全的拼SQL(JDBC同理):
对于常见的SQL注入采用预编译就行了,但是很多时候条件较多或较为复杂的时候很多人都想偷懒拼SQL。 写了个这样的多条件查询条件自动匹配:
public static String SQL_FORUM_CLASS_SETTING = "SELECT * from bjcyw_forum_forum where 1=1 "; public List