0x1 cgi
- cgi可以理解为最原始的动态web实现方式
- 本质上是通过可执行文件(bash,python,二进制文件等)方式来实现来生成动态web
- 本章本着最小化的原则利用python实现cgi服务器,使用python实现基本功能
0x2 python.cgi
- 使用python3内置的方法开启cgi服务器(本身也提供web服务)
python -m http.server --cgi 80
- 默认只会对
/cgi-bin/
目录下的文件启动cgi服务 - 需要注意的是
后缀名
没有什么关系 - 访问时一定要写cgi的全url,直接访问cgi目录会403
- 访问
http://127.0.0.1/cgi-bin/test.cgi
test.cgi
内容如下
#!/usr/bin/python3
print("Content-Type: text/html\n")
print("""
<!doctype html>
<title>Hello</title>
<h2>hello world</h2>
"""
)
- 使用
os.environ[key]
来获取header/环境变量信息,参考show_env.cgi
0x3 自定义cgi目录
- 可以对源码http.server中的字段
cgi_directories
来控制cgi目录 - 修改的http.server版本一定要和当前python匹配
- 将
/cus
添加到cgi
解析目录
cgi_directories = ['/cgi-bin', '/htbin','/cus']
- 将所有文件都当作
cgi
解析
cgi_directories = ['/cgi-bin', '/htbin','/']
0x4 GET/POST
- 如果以
key/value
+get/post
的方式来传递数据,可以通过FieldStorage
来获取 get_post.cgi
实现如下
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename:test.py
# CGI处理模块
import cgi, cgitb
# 创建 FieldStorage 的实例化
form = cgi.FieldStorage()
# 获取GET/post数据
site_name = form.getvalue('name')
site_pwd = form.getvalue('pwd')
print("Content-type:text/html\n")
print("name:%s\r\npwd:%s" % (site_name, site_pwd))
- 通过get访问:
curl "http://127.0.0.1/get_post.cgi?name=testname&pwd=password"
- 通过post访问:
curl -d "name=testname&pwd=password" http://127.0.0.1/get_post.cgi
0x5 RAW_POST
- 某些时候数据是以raw的方式传递的
- 这个时候需要cgi能够接收到原始的post数据
- 服务端实现如下
get_raw_post.cgi
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename:test.py
# CGI处理模块
import os,sys
import cgi, cgitb
import json
# http声明
print("Content-type:text/html\n")
# 获取原始data数据
content_len = int(os.environ["CONTENT_LENGTH"])
raw_data = sys.stdin.read(content_len)
print("======RAW=============")
print(raw_data)
print("======RAW=============")
# 加载json
print("======GET PHONE=============")
json_read = json.loads(raw_data)
print("phone is:"+json_read["phone"])
- 使用curl模拟发送原始的json文件
curl \
-H "Accept: application/json" \
-H "Content-type: application/json" \
-X POST -d \
'{"phone": "12345678","password": "pwd"}' \
http://127.0.0.1/get_raw_post.cgi
- 可以解析出json中的字段
0x6 HTTPBasicAuth
- python内置的httpserver过滤掉了自定义非200 httpstatus,因此不能通过response通知客户端登陆
- 此外即使想要想实现接收HTTPBasicAuth也需要对httpserver稍加修改
- 本案例以httpserver3.8为例,罗列几个核心修改点
1275行处,修改默认端口为80
parser.add_argument('port', action='store', default=80, type=int, nargs='?', help='Specify alternate port [default: 80]')
1279行处,永远开启cgi
if 1: handler_class = CGIHTTPRequestHandler else: handler_class = partial(SimpleHTTPRequestHandler, directory=args.directory)
1025行处,对所有目录开启cgi解析
cgi_directories = ['/cgi-bin', '/htbin','/']
1115行处增加HTTPBasicAuth中的密码传递功能
env['REMOTE_USER'] = authorization[0] env['REMOTE_PWD'] = authorization[1]
- 最终的的 server.py 在这里
- cgi中实现HTTPBasicAuth的逻辑代码如下
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# filename:test.py
# CGI处理模块
import os,sys
import cgi, cgitb
import json
def authenticate():
# 检查是否有认证
if "REMOTE_USER" in os.environ and "REMOTE_PWD" in os.environ:
name=os.environ["REMOTE_USER"]
pwd=os.environ["REMOTE_PWD"]
# 账号密码匹配
if name=="test" and pwd=="123456":
print("[+] great")
else:
# 输出错误的账号密码
print("[+] wrong account")
print("[!] name:"+name)
print("[!] pwd:"+pwd)
else:
print("[+] 401:need auth")
return 0
# http声明
print("Content-type:text/html\n")
authenticate()
- 使用curl作为测试代码
curl --basic http://127.0.0.1/http_auth.cgi
curl --basic -u test:123456 http://127.0.0.1/http_auth.cgi
curl --basic -u user:pass http://127.0.0.1/http_auth.cgi