Lighttpd机制研究

前言

在路由器安全研究中,web服务是主要攻击面之一。

常见的路由器web框架类型有以下几种:

1.一个web主程序包揽所有任务,仅有简单的前端文件,无cgi程序。虽然听起来很离谱,但是确实有厂商的路由器把大部分功能直接丢到web主程序里面执行,由web主程序来直接执行命令与本地交互。

2.前端+web主程序+后端,其中后端由cgi程序实现。

3.前端+web主程序+后端,其中后端由解析器+脚本实现,常见的有luci+lua。

比较常用的现成框架有lighttpd、apache、nginx等几种,本文以一个使用了lighttpd的路由器为例来进行探讨。该lighttpd使用的是第二种:前端+web主程序+后端,其中后端由cgi程序实现。

lighttpd框架简述

一、基础架构:

客户端(浏览器) ←→ web服务主程序 ←→ 后端程序(即cgi,可以各种语言实现) ←→ 数据库
                     ↑
                     ↓
               静态资源(前端)

二、前端部分:静态资源的处理流程

前端通常由 HTML、CSS、JavaScript、图片等静态文件组成,web主程序可直接处理这些文件,无需后端参与。

客户端请求静态资源的过程:

  • 步骤 1:用户访问 http://服务器IP/index.html
  • 步骤 2:浏览器发送对应请求给web主程序
  • 步骤 3:web主程序接收请求,解析 URL 得知需要访问 /var/www/html/index.html
  • 步骤 4:web主程序生成 HTTP 响应(包含 Content-Type: text/html 等头信息),将 index.html 内容返回给浏览器。
  • 步骤 5:浏览器解析 HTML,发现引用了 css/style.css 和 js/app.js,再次向web主程序发送请求,重复上述流程获取这些资源并渲染页面。

三、后端部分:动态请求的处理流程

像修改本地配置、用户认证等需要涉及数据库的内容,就需要与后端交互了。

客户端请求动态资源的过程

  • 步骤 1:用户通过前端页面输入账号密码
  • 步骤 2:浏览器生成对应请求,发送给Lighttpd。
  • 步骤 3:web主程序根据根据请求,把请求里的参数解析出来,调用对应的cgi程序进行处理
  • 步骤 5:后端的cgi程序执行验证逻辑,生成响应内容,返回给web主程序。
  • 步骤 6:web主程序将后端响应包装成 HTTP 响应,返回给浏览器,前端 JS 接收并处理结果。

lighttpd的工作流程具体分析

启动项和配置分析

学习lighttpd可以直接看源码,本文则通过逆向一个路由器来分析,重点放在lighttpd的web主程序与cgi的交互上。

lighttpd的主程序名字就是lighttpd,首先通过启动项来看该路由器是如何启动lighttpd程序的,启动项是/etc/init.d目录下的lighttpd,这个lighttpd是个bash文件,核心语句是:

service_start /usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf

通过lighttpd.conf配置来启动lighttpd主程序,如图所示:

接着跟进lighttpd.conf,该配置文件指定了监听端口、默认路径等基本操作,重点在于模块的调用,它只调用了”mod_redirect”这个模块,其他的全都注释掉,如图所示:

跟进 mod_redirect,发现搜不到,只有mod_redirect.so,这个应该就是模块,搞成了动态链接库,至于server.modules(“mod_redirect”)是如何转化成加载mod_redirect.so的,应该是在lighttpd程序中才能找到了。

继续往下看,lighttpd.conf中出现了include “/etc/lighttpd/conf.d/*.conf”:

找到这个目录:

查看这几个conf文件,发现全都是如下内容:

也就是说虽然前面server.modules()把其他模块都注释掉了,但后面又把他们导进来使用。

主程序分析

初始化

现在可以开始lighttpd主程序的分析了,ida打开,main函数一开始通过 sub_40C14C 完成基础环境准备,包括解析命令行参数、处理环境变量、设置信号处理机制等,确保服务器能在正确的环境中启动。若初始化失败,程序会直接退出。随后,sub_407994 创建服务器核心上下文结构(v5),这个结构类似 “总管”,存储着服务器的配置、连接状态、插件信息等关键数据,为后续运行提供基础。

接着是核心运行阶段。sub_40988C 函数承担核心初始化工作,包括解析配置文件(如端口、根目录、插件列表)、加载插件(如 mod_cgi 处理 CGI 请求)、初始化网络监听等。若初始化成功,服务器进入事件循环(sub_40BF5C)—— 这是服务器的 “心脏”,通过 fdevent_poll 持续监听网络事件(如客户端连接、数据读写),并调用对应模块处理请求,直到收到停止信号或超时。

最后是资源清理与循环控制阶段,此处略过。

sub_40988C函数中实现了.so模块的加载和初始化,流程为:

1.调用 plugins_load来加载:
→ 解析配置中的 server.modules
→ 动态加载插件 .so 文件到内存
2.调用 plugins_call_init来初始化:
→ 执行.so 插件初始化函数
→ 注册.so 插件的核心处理回调

简单来说就是,sub_40988C函数在加载.so 文件到内存后,会调用plugins_call_init来调用该.so 文件的初始化函数(初始化函数在.so中),把该函数的各个回调函数注册到lighttpd主程序初始化时候创建的结构体(plugin 结构体)中,后面通过结构体指针就可以很便捷地访问各个模块的回调函数了。

mod_cgi.so模块是专门负责cgi程序调用的模块,以它为例子,其初始化函数为mod_cgi_plugin_init,会把它的各个回调函数注册到a1这个结构体中,a1由plugins_call_init函数传参,是plugin 结构体,如图所示:

所谓回调函数,指的就是在某个特定条件满足之后会触发的函数,mod_cgi.so的如下:

  • a1[13] = sub_19AC;:配置解析与请求匹配函数,负责解析 cgi.assign 等配置,判断当前请求是否需要用 CGI 处理(如匹配 .cgi 后缀)。
  • a1[14] = sub_3EB4;:CGI 核心处理函数,负责创建管道、fork 子进程、执行 CGI 程序、建立与 CGI 进程的通信。
  • a1[5] = sub_1EA4;:请求处理开始阶段的回调(如初始化 CGI 处理上下文)。
  • a1[2] = sub_20B4;:配置加载回调(解析并验证 CGI 相关配置项)。
  • a1[4] = sub_1660;:模块内部资源初始化(如缓冲区、状态变量)。
  • a1[3] = sub_132C;:模块状态检查回调(如监控 CGI 进程池状态)。

事件循环处理

初始化成功后,main函数调用sub_40BF5C来进入事件循环处理,流程如下:

  1. 初始化事件监控
    循环开始前,函数会初始化事件监控机制(通过 fdevent 库),将服务器的监听套接字、客户端连接的文件描述符、CGI 进程的通信管道等事件源注册到监控列表中,并关联对应的处理回调(如 “客户端连接事件” 绑定连接处理函数,“CGI 管道可读事件” 绑定 mod_cgi 的输出处理函数)。(注:这一步在核心初始化的时候就已经完成,而不是在sub_40BF5C函数中完成)
  2. 等待事件触发(阻塞阶段)
    调用 fdevent_poll 函数(事件循环的核心阻塞点),进入等待状态。fdevent_poll 会阻塞等待,直到有事件发生(如客户端连接到达、数据可读 / 可写)。
  3. 处理触发的事件
    当事件触发后,函数会遍历所有触发的事件,根据事件类型(读 / 写 / 异常)和事件源(网络套接字 / 管道),调用预先注册的回调函数:
    • 新客户端连接:调用连接建立函数(如 connection_accept,这个函数在lighttpd程序里),创建新连接并注册后续读写事件;
    • 客户端数据可读:调用请求读取函数(如 connection_read,这个函数在lighttpd程序里),解析 HTTP 请求;
    • CGI 管道可读:调用 mod_cgi 的 cgi_handle_output 函数,读取 CGI 程序的输出并转发给客户端;
    • 客户端可写:调用响应发送函数(如 connection_write),将处理结果发送给客户端。
  4. 清理退出

也就是说,事件循环处理函数sub_40BF5C首先通过监控机制来监听各种事件源,当对应的事件被触发时,就会调用对应的回调函数来进行处理。

同样以mod_cgi.so为例,在它的回调函数中,负责CGI核心处理的函数是sub_3EB4,核心流程如下:

CGI进程启动

  • 创建双向管道(pipe系统调用)
  • 通过fork()创建子进程:
    • 子进程端:
      • 重定向标准输入/输出(dup2
      • 设置环境变量(包括LD_PRELOAD等)
      • 切换工作目录(chdir
      • 关闭所有非必要文件描述符(3-255循环关闭)
      • 最终执行CGI程序(execve
    • 父进程端:
      • 管理管道文件描述符
      • 注册事件监听器(fdevent_register
      • 设置非阻塞IO(fdevent_fcntl_set_nb

    执行CGI程序的代码如图所示:

    可供调用的cgi函数在cgi-bin文件夹中:

    总结

    综上所述,就得到了lighttpd与后端cgi程序的交互流程:

    首先,lighttpd程序通过lighttpd.conf配置文件来进行初始化,加载指定的各种模块(.so文件),其中mod_cgi.so文件是负责cgi程序调用的核心模块,在加载了mod_cgi.so模块后,注册其回调函数到plugin结构体中,并与对应事件进行关联,之后进入事件循环监听处理阶段,当需要调用某个cgi的请求从前端发过来之后,会触发事件,被lighttpd程序监听到之后,它会调用实现关联的注册函数进行处理,即mod_cgi.so模块中的对应回调函数,假设触发的是sub_3EB4函数,则会根据lighttpd传参过来的cgi程序名字和路径,调用execve来执行这个cgi程序,实现lighttpd程序与cgi程序的交互。

    暂无评论

    发送评论 编辑评论

    
    				
    |´・ω・)ノ
    ヾ(≧∇≦*)ゝ
    (☆ω☆)
    (╯‵□′)╯︵┴─┴
     ̄﹃ ̄
    (/ω\)
    ∠( ᐛ 」∠)_
    (๑•̀ㅁ•́ฅ)
    →_→
    ୧(๑•̀⌄•́๑)૭
    ٩(ˊᗜˋ*)و
    (ノ°ο°)ノ
    (´இ皿இ`)
    ⌇●﹏●⌇
    (ฅ´ω`ฅ)
    (╯°A°)╯︵○○○
    φ( ̄∇ ̄o)
    ヾ(´・ ・`。)ノ"
    ( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
    (ó﹏ò。)
    Σ(っ °Д °;)っ
    ( ,,´・ω・)ノ"(´っω・`。)
    ╮(╯▽╰)╭
    o(*////▽////*)q
    >﹏<
    ( ๑´•ω•) "(ㆆᴗㆆ)
    😂
    😀
    😅
    😊
    🙂
    🙃
    😌
    😍
    😘
    😜
    😝
    😏
    😒
    🙄
    😳
    😡
    😔
    😫
    😱
    😭
    💩
    👻
    🙌
    🖕
    👍
    👫
    👬
    👭
    🌚
    🌝
    🙈
    💊
    😶
    🙏
    🍦
    🍉
    😣
    Source: github.com/k4yt3x/flowerhd
    颜文字
    Emoji
    小恐龙
    花!
    上一篇
    下一篇