jsp select和文字在同一行_Jsp挖掘(8)-JSP线程安全

9162f52acb9fbbf961e37c9483b119ea.png

JSP线程安全

一、线程漏洞成因

(1)实例变量

实例变量是在堆中分配的,并被属于该实例的所有线程共享,所以不是线程安全的.

(2)JSP系统提供的8个类变量

JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是线程安全的,APPLICATION在整个系统内被使用,所以不是线程安全的.

(3)局部变量

局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以是线程安全的.

(4)静态类

静态类不用被实例化,就可直接使用,也不是线程安全的.

(5)外部资源:

在程序中可能会有多个线程或进程同时操作同一个资源(如:多个线程或进程同时对一个文件进行写操作).此时也要注意同步问题.

二、实例子操作

开启环境

42e77d74263315eb59e86a4db9b71cbd.png

线程需要多个线程:开启两个网页页面

df00f948abeb8bf5fb0fa40f1a164b3a.png

两个线程同时提交不同的姓名:

cd8b081b0b49e44cdcd6e42c60bb9255.png

但是返回四第二个点击的dave的名字

a262f28d6c8ac136be61264349245d6c.png

第一个线程和第二个线程的内容串起来了

正常的返回应该是不同的查询返回各自对应的值

9e8f05b035b7f2477834a796239700ca.png

查看系统源码:

源代码 private final static String USER_NAME = "username"; private static String currentUser; private String originalUser; /** * Description of the Method * * @param s * Description of the Parameter * @return Description of the Return Value */ protected Element createContent(WebSession s) { ElementContainer ec = new ElementContainer(); try { Connection connection = DatabaseUtilities.getConnection(s); ec.addElement(new StringElement("Enter user name: ")); ec.addElement(new Input(Input.TEXT, USER_NAME, "")); currentUser = s.getParser().getRawParameter(USER_NAME, ""); originalUser = currentUser; // Store the user name String user1 = new String(currentUser); Element b = ECSFactory.makeButton("Submit"); ec.addElement(b); ec.addElement(new P()); if (!"".equals(currentUser)) { Thread.sleep(1500); // Get the users info from the DB String query = "SELECT * FROM user_system_data WHERE user_name = '" + currentUser + "'"; Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet results = statement.executeQuery(query); if ((results != null) && (results.first() == true)) { ec.addElement("Account information for user: " + originalUser + "<br><br>"); ResultSetMetaData resultsMetaData = results.getMetaData(); ec.addElement(DatabaseUtilities.writeTable(results, resultsMetaData)); } else { s.setMessage("'" + currentUser + "' is not a user in the WebGoat database."); } } if (!user1.equals(currentUser)) { makeSuccess(s); } } catch (Exception e) { s.setMessage("Error generating " + this.getClass().getName()); e.printStackTrace(); } return (ec); } -------------------------------------------------------------------- 上面是关键的数据提交处理代码:成因过程分析 存在问题 private static String currentUser;//这个currentUser被定义成静态的变量 在查询获取数据 currentUser = s.getParser().getRawParameter(USER_NAME, ""); 后面提交设置线程休息1.5s,再现实查询的结果 Thread.sleep(1500); 这个时候我们如果出发一个新的线程去请求参数,重新执行一次上面的查询过程,就会将第一次的查询 currentUser 的值, 覆盖掉,这个时候第一个用户查询数值就变成第二个用户查询的数值。 调用查询语句:String query = "SELECT * FROM user_system_data WHERE user_name = '" + currentUser + "'";

购物车购物的例子:

a7aa065ac8620bd450557b3f140a04f7.png

分别第一个线程下单一个,第二个线程下单三个

ae636d52f934f9388820262f4932c234.png

以低的价格购买更多的物品

代码分析:

源代码: protected Element createContent(WebSession s) { ElementContainer ec = null; try { String submit = s.getParser().getStringParameter("SUBMIT"); if ("Purchase".equalsIgnoreCase(submit)) { updateQuantity(s); ec = createPurchaseContent(s, quantity1, quantity2, quantity3, quantity4); } else if ("Confirm".equalsIgnoreCase(submit)) { ec = confirmation(s, quantity1, quantity2, quantity3, quantity4); // Discount if (calcTOTAL == 0) // No total cost for items { discount = 0; // Discount meaningless } else // The expected case -- items cost something { ratio = runningTOTAL / calcTOTAL; } if (calcTOTAL > runningTOTAL) { // CONGRATS discount = (int) (100 * (1 - ratio)); s.setMessage("Thank you for shopping! You have (illegally!) received a " + discount + "% discount. Police are on the way to your IP address."); makeSuccess(s); } else if (calcTOTAL < runningTOTAL) { // ALMOST discount = (int) (100 * (ratio - 1)); s.setMessage("You are on the right track, but you actually overpaid by " + discount + "%. Try again!"); } } else { updateQuantity(s); ec = createShoppingPage(s, quantity1, quantity2, quantity3, quantity4); } } catch (ParameterNotFoundException pnfe) { // System.out.println("[DEBUG] no action selected, defaulting to createShoppingPage"); ec = createShoppingPage(s, quantity1, quantity2, quantity3, quantity4); } return ec; } ------------------------------------------------------------ 相关过程分析: 窗口A 开始购物车提交货物代码执行的处,购买一个总计:$169: if ("Purchase".equalsIgnoreCase(submit)) { updateQuantity(s); ec = createPurchaseContent(s, quantity1, quantity2, quantity3, quantity4); } 结果calcTOTAL = runningTOTAL=169 这个相关提交购买的数量,价格的计算。 窗口B: 这个时候另外一个线程提交参数 Update Cart更新的购物车的数量和产品类型。 { updateQuantity(s); ec = createShoppingPage(s, quantity1, quantity2, quantity3, quantity4); } 调用 createShoppingPage() 更新购物相关信息。 结果subtotal=3*1799=5397 窗口A 点击confirm执行 结果runningTOTAL=169,calcTOTAL =5397 //主要是原因:和上面的原因也是一样,也是将变量变成静态变量 private static int total = 0; private static float runningTOTAL = 0; private static int subTOTAL = 0; private static float calcTOTAL = 0; private static int quantity1 = 0; private static int quantity2 = 0; private static int quantity3 = 0; private static int quantity4 = 0; private float ratio = 0; private int discount = 0;

修复方案:避免使用static修饰符,因为static的变量,每个用户都有对它的相关操作权限。

三、防护总结:

修复方案:避免使用static修饰符,因为static的变量,每个用户都有对它的相关操作权限。

相关修复的材料:

https://wenku.baidu.com/view/20122f8483d049649b665800.html(参考材料)

(1)采用单线程方式

在该JSP文件中加上: <%@ page isThreadSafe="false" %>,使它以单线程方式执行,这时,仍然只有一个实例,所有客户端的请求以串行方式执行。这样会降低系统的性能.

(2)对函数savebuy()加synchronized进行线程同步,该JSP仍然以多线程方式执行,但也会降低系统的性能.

public synchronized void savebuy()

{

......

}

(3)采用局部变量代替实例变量,函数savebuy()声明如下:

因为在savebuy()中使用的是传给他的形参,是在堆栈中分配的,所以是线程安全的.

public void savebuy(String name,String product, int quantity)

{

......

}

调用方式改为:

<%

String name

String product;

long quantity;

name=request.getParameter("name");

product=request.getParameter("product");

quantity=request.getParameter("quantity");

savebuy(name,product,quantity)

%>

如果savebuy的参数很多,或这些数据要在很多地方用到,也可声明一个类,并用他做参数,如:

public class buyinfo

{

String name;

String product;

long quantity;

}

public void savebuy(buyinfo info)

{

......

}

调用方式改为:

<%

buyinfo userbuy = new buyinfo();

userbuy.name=request.getParameter("name");

userbuy.product=request.getParameter("product");

userbuy.quantity=request.getParameter("quantity");

savebuy(userbuy);

%>

所以最好是用(3),因为(1),(2)会降低系统的性能.

多线程问题一般只有在在大并发量访问时,才有可能出现,并且很难重复出现,所以应在编程时就时刻注意。

四、学习了解

材料:

https://www.cnblogs.com/adhzl/p/12041056.html

https://blog.csdn.net/bigfoolish/article/details/83878542

​公众号:

daaad7b2589fc8233d4857a2dbdb5fc7.png

thelostworld:

5fbdd30f669784f50db9267a0d6845c9.png

个人知乎​:https://www.zhihu.com/people/fu-wei-43-69/columns

​个人简书:https://www.jianshu.com/u/bf0e38a8d400

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值