#422·OneForAll

模块-证书透明度查询-censys 接口过期(不可用)了

Author: gaogzhenCreated Sep 15, 2026Updated Sep 15, 2026
Labelsbug

是否使用了最新代码

Bug描述 censys 子域名查询接口不可用

运行环境

  • 系统:[例如Windows 10 x64](必写)
  • Python版本:[例如3.7.1](必写)
  • OneForAll版本:[例如0.3.0](必写)

如何复现 复现步骤(选写)

复现命令(必写)

报错文本 复制完整的报错文本(必写)

预期结果 清晰而简洁的预期结果描述(选写,如正常情况应该是怎么样的)

实际结果 清晰而简洁的实际结果描述(选写,如出现什么错误)

屏幕截图

Image

日志上传 上传oneforall.log日志文件(复杂问题建议上传)

其他补充 1 censys 不在支持传统搜索,详情地址https://censys.com/blog/legacy-search-deprecation 2 新注册账号没有API ID 和Secret,个人免费账号权限标志改为:persional_access_token

通过python sdk 简单改写代码:

import traceback

from censys_platform import SDK
from censys_platform.models.errormodel import ErrorModel

from common.query import Query
from config import settings
from config.log import logger


class CensysPlatformAPI(Query):
    """
    tag: 当前不适合免费用户
    """
    def __init__(self, domain):
        Query.__init__(self)
        self.domain = domain
        self.module = 'Certificate'
        self.source = "CensysPlatformAPIQuery"
        self.personal_access_token = settings.censys_personal_access_token
        self.delay = 3.0  # Censys 接口查询速率限制 最快2.5秒查1次
        self.sdk = SDK(settings.censys_personal_access_token)
        # 模块启用标志:遇到 403/401 等永久性错误时置为 False
        self.enabled = True

    # ---------------------------------------------------------------
    # 查询入口
    # ---------------------------------------------------------------
    def query(self):
        """
        向接口查询子域并做子域匹配

        分层异常处理策略:
            1. ErrorModel      —— Censys 业务错误(401/403/429/5xx)
            2. AttributeError  —— 响应结构异常
            3. TimeoutError    —— 网络超时
            4. Exception       —— 兜底捕获
        """
        # ---------- 0. 提前退出:模块已被禁用 ----------
        if not self.enabled:
            logger.log('DEBUG', f'[{self.source}] Module disabled, skip query')
            return

        # ---------- 1. 构造查询参数 ----------

        self.header = self.get_header()
        self.proxy = self.get_proxy(self.source)

        escaped_domain = self.domain.replace('.', r'\.')
        query_str = f"cert.names=~`.*\\..*\\.{escaped_domain}$`"
        params = {"query": query_str}

        logger.log('DEBUG', f'[{self.source}] Censys query: {query_str}')


        # ---------- 2. 调用 API(分层捕获) ----------
        try:
            # todo 后续接口返回数据适配
            resp = self.sdk.global_data.search(search_query_input_body=params)

        except ErrorModel as e:
            # -------- 2.1 Censys 业务错误 --------
            e = e.data
            status = getattr(e, 'status', None)
            detail = getattr(e, 'detail', None) or getattr(e, 'title', None)

            if status == 403:
                # 权限错误:永久性,禁用模块并提前退出
                logger.log('FATAL', f'[{self.source}] API access forbidden: {detail}')
                self.enabled = False
                return

            if status == 401:
                # 认证失败:永久性,禁用模块
                logger.log('FATAL', f'[{self.source}] Authentication failed: {detail}')
                self.enabled = False
                return

            if status == 429:
                # 限流:临时性,记录后返回
                logger.log('ALERT', f'[{self.source}] Rate limited (429): {detail}')
                logger.log('DEBUG', traceback.format_exc())
                return

            if status and 500 <= status < 600:
                # 服务端错误:临时性
                logger.log('ALERT', f'[{self.source}] Server error ({status}): {detail}')
                logger.log('DEBUG', traceback.format_exc())
                return

            # 其他业务错误
            logger.log('ALERT', f'[{self.source}] Censys API error '
                                f'(status={status}): {detail}')
            logger.log('DEBUG', traceback.format_exc())
            return

        except AttributeError as e:
            # -------- 2.2 响应结构异常 --------
            logger.log('ERROR', f'[{self.source}] Invalid response structure: {e.args}')
            logger.log('DEBUG', traceback.format_exc())
            return

        except TimeoutError as e:
            # -------- 2.3 网络超时 --------
            logger.log('ALERT', f'[{self.source}] Request timeout: {e.args}')
            logger.log('DEBUG', traceback.format_exc())
            return

        except Exception as e:
            # -------- 2.4 兜底捕获 --------
            logger.log('ERROR', f'[{self.source}] Unexpected error while querying '
                                f'{self.domain}: {e.args}')
            logger.log('DEBUG', traceback.format_exc())
            return

        # ---------- 3. 空响应检查 ----------
        if not resp:
            logger.log('ALERT', f'[{self.source}] Empty response for {self.domain}')
            return

        # ---------- 4. 提取可解析文本 ----------
        try:
            text = self._extract_text(resp)
            if not text:
                logger.log('ALERT', f'[{self.source}] Response has no parsable text')
                return
        except Exception as e:
            logger.log('ERROR', f'[{self.source}] Failed to extract text: {e.args}')
            logger.log('DEBUG', traceback.format_exc())
            return

        # ---------- 5. 匹配子域名 ----------
        try:
            subdomains = self.match_subdomains(text)
            if not subdomains:
                logger.log('DEBUG', f'[{self.source}] No subdomain matched '
                                    f'for {self.domain}')
                return

            self.subdomains.update(subdomains)
            logger.log('INFOR', f'[{self.source}] Found {len(subdomains)} '
                                f'subdomains for {self.domain}')

        except Exception as e:
            logger.log('ERROR', f'[{self.source}] Failed to match subdomains: {e.args}')
            logger.log('DEBUG', traceback.format_exc())
            return

    # ---------------------------------------------------------------
    # 辅助方法
    # ---------------------------------------------------------------
    @staticmethod
    def _extract_text(resp):
        """
        从响应对象中提取可解析的文本。

        兼容多种响应形态:
            - 带 .text 属性的对象
            - dict
            - 其他可转换为字符串的对象
        """
        if resp is None:
            return None
        if hasattr(resp, 'text'):
            return resp.text
        if isinstance(resp, dict):
            return resp.get('text') or str(resp)
        return str(resp)

    # ---------------------------------------------------------------
    # 执行入口
    # ---------------------------------------------------------------
    def run(self):
        """
        类执行入口
        """
        if not self.have_api(self.personal_access_token):
            return
        # 若模块在之前的调用中已被禁用,直接跳过
        if not self.enabled:
            logger.log('DEBUG', f'[{self.source}] Module disabled, skip run')
            return
        self.begin()
        self.query()
        self.finish()
        self.save_json()
        self.gen_result()
        self.save_db()


def run(domain):
    """
    类统一调用入口

    :param str domain: 域名
    """
    query = CensysPlatformAPI(domain)
    query.run()


if __name__ == '__main__':
    run('baidu.com')
    # query = CensysPlatformAPI("baidu.com")
    # query.begin()

测试结果: 13:36:48,993 [FATAL] censys_platform_api:69 - [CensysPlatformAPIQuery] API access forbidden: This endpoint requires an organization ID for API access. Free users can only access this endpoint through the Platform UI. 13:36:48,994 [INFOR] module:63 - CensysPlatformAPIQuery module took 1.6 seconds found 0 subdomains 总结:查询子域名接口对免费用户不适用,选项一:通过web UI爬虫爬取;选项二:升级plan,即付费