├── arl_web.log ├── arl_worker.log ├── mongo-init.js ├── README.md ├── docker-compose.yml ├── config-docker.yaml ├── nginx.conf └── 1.py /arl_web.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arl_worker.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mongo-init.js: -------------------------------------------------------------------------------- 1 | db.user.drop() 2 | db.user.insert({ username: 'admin', password: hex_md5('arlsalt!@#'+'arlpass') }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 灯塔ARL 2 | 灯塔v2.6.2_docker版本 3 | 4 | 本仓库作者:青山 5 | 6 | 7 | 8 | ## 安装教程 9 | 10 | ### 1.安装docker 11 | 12 | ```shell 13 | yum install docker 14 | ``` 15 | 16 | ### 2.配置config-docker.yaml 17 | 18 | ```shell 19 | vim config-docker.yaml 20 | ``` 21 | ### 3.安装arl 22 | 23 | ```shell 24 | docker volume create arl_db 25 | 26 | docker-compose up -d 27 | ``` 28 | ### 4.添加指纹 29 | 30 | ```shell 31 | python ./1.py https://127.0.0.1:5003/ admin arlpass old 32 | ``` 33 | 34 | ## Tips 35 | 36 | 1.docker与docker-compose都需要安装 37 | 38 | 2.自行开放5003端口 39 | 40 | 3.默认账号密码 admin/arlpass 41 | 42 | ## 问题 43 | 1.external volume "arl_db" not found 44 | 45 | 答: 46 | ```shell 47 | docker volume create arl_db 48 | ``` 49 | 2.提示配置yaml文件 'version'的报错 50 | 51 | 答:删除配置yaml文件第一行version即可 52 | 53 | 3.拉取镜像失败 54 | 55 | 答:https://docker.1panel.live/ (正常访问即可使用) 56 | ```shell 57 | sudo tee /etc/docker/daemon.json <<-'EOF' 58 | { 59 | "registry-mirrors": [ 60 | "https://docker.1panel.live/" 61 | ] 62 | } 63 | EOF 64 | ``` 65 | 重启docker即可 66 | ```shell 67 | systemctl restart docker 68 | ``` 69 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | volumes: 4 | arl_db: 5 | external: true 6 | 7 | services: 8 | web: 9 | image: qssec/arl:v2.6.2 10 | container_name: arl_web 11 | restart: unless-stopped 12 | depends_on: 13 | - mongodb 14 | - rabbitmq 15 | ports: 16 | #http 服务,默认不映射出来 17 | #- "5003:80" 18 | - "5003:443" 19 | volumes: 20 | - ./arl_web.log:/code/arl_web.log 21 | - ./config-docker.yaml:/code/app/config.yaml 22 | - ./image:/code/app/tmp_screenshot 23 | - ./poc:/opt/ARL-NPoC/xing/plugins/upload_poc 24 | entrypoint: ["sh", "-c", "gen_crt.sh; nginx; wait-for-it.sh mongodb:27017; wait-for-it.sh rabbitmq:5672; gunicorn -b 0.0.0.0:5003 app.main:arl_app -w 3 --access-logfile arl_web.log"] 25 | environment: 26 | - LANG=en_US.UTF-8 27 | - TZ=Asia/Shanghai 28 | 29 | worker: 30 | image: qssec/arl:v2.6.2 31 | container_name: arl_worker 32 | restart: unless-stopped 33 | depends_on: 34 | - mongodb 35 | - rabbitmq 36 | volumes: 37 | - ./arl_worker.log:/code/arl_worker.log 38 | - ./config-docker.yaml:/code/app/config.yaml 39 | - ./image:/code/app/tmp_screenshot 40 | - ./poc:/opt/ARL-NPoC/xing/plugins/upload_poc 41 | entrypoint: ["sh", "-c", "wait-for-it.sh mongodb:27017; wait-for-it.sh rabbitmq:5672; 42 | celery -A app.celerytask.celery worker -l info -Q arlgithub -n arlgithub -c 2 -O fair -f arl_worker.log & 43 | celery -A app.celerytask.celery worker -l info -Q arltask -n arltask -c 2 -O fair -f arl_worker.log"] 44 | 45 | environment: 46 | - LANG=en_US.UTF-8 47 | - TZ=Asia/Shanghai 48 | 49 | scheduler: 50 | image: qssec/arl:v2.6.2 51 | container_name: arl_scheduler 52 | restart: unless-stopped 53 | depends_on: 54 | - mongodb 55 | - rabbitmq 56 | volumes: 57 | - ./config-docker.yaml:/code/app/config.yaml 58 | entrypoint: [ "sh", "-c", "wait-for-it.sh mongodb:27017; wait-for-it.sh rabbitmq:5672; python3.6 -m app.scheduler" ] 59 | environment: 60 | - LANG=en_US.UTF-8 61 | - TZ=Asia/Shanghai 62 | 63 | mongodb: 64 | image: mongo:4.0.27 65 | container_name: arl_mongodb 66 | restart: always 67 | volumes: 68 | - arl_db:/data/db 69 | - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro 70 | environment: 71 | - MONGO_INITDB_DATABASE=arl 72 | - MONGO_INITDB_ROOT_USERNAME=admin 73 | - MONGO_INITDB_ROOT_PASSWORD=admin 74 | 75 | 76 | rabbitmq: 77 | image: rabbitmq:latest 78 | container_name: arl_rabbitmq 79 | restart: always 80 | environment: 81 | - RABBITMQ_DEFAULT_PASS=arlpassword 82 | - RABBITMQ_DEFAULT_USER=arl 83 | - RABBITMQ_DEFAULT_VHOST=arlv2host 84 | logging: 85 | driver: "json-file" 86 | options: 87 | max-size: "1M" 88 | max-file: "10" 89 | -------------------------------------------------------------------------------- /config-docker.yaml: -------------------------------------------------------------------------------- 1 | CELERY: 2 | BROKER_URL : "amqp://arl:arlpassword@rabbitmq:5672/arlv2host" 3 | 4 | 5 | MONGO: 6 | URI : 'mongodb://admin:admin@mongodb:27017/' 7 | DB : 'arl' 8 | 9 | 10 | 11 | #GeoIP 数据库文件配置项 12 | GEOIP: 13 | CITY: '/data/GeoLite2/GeoLite2-City.mmdb' 14 | ASN: '/data/GeoLite2/GeoLite2-ASN.mmdb' 15 | 16 | 17 | #Fofa API 配置项 18 | FOFA: 19 | URL: "https://fofa.info" 20 | EMAIL: "" 21 | KEY: "" 22 | 23 | 24 | 25 | # 利用三方API进行域名收集 26 | # 测试命令 python3.6 -m test.test_query_plugin [source1] [source2] 27 | # 域名查询插件配置, enable 字段可以单独控制是否启用该插件,如果没有这个字段也会默认启用 28 | QUERY_PLUGIN: 29 | alienvault: 30 | enable: true 31 | certspotter: # https://www.certspotter.com/ 32 | after_id: 0 33 | max_page: 3 34 | enable: true 35 | crtsh: 36 | enable: true 37 | fofa: # fofa 的key配置在上面 38 | enable: true 39 | hunter_qax: # 网站 https://hunter.qianxin.com/ 40 | api_key: 41 | page_size: 100 42 | max_page: 5 43 | enable: false 44 | passivetotal: # 请前往 https://community.riskiq.com/ 注册自己的key并替换 45 | auth_email: 46 | auth_key: 47 | enable: false 48 | quake_360: # 网站 https://quake.360.cn/ 49 | quake_token: 50 | max_size: 500 51 | enable: false 52 | rapiddns: 53 | enable: true 54 | securitytrails: # 网站 https://securitytrails.com/ 55 | api_key: 56 | enable: false 57 | threatminer: 58 | enable: true 59 | virustotal: # 网站 https://www.virustotal.com/gui/ 60 | api_key: 61 | enable: false 62 | zoomeye: 63 | api_key: 64 | max_page: 20 65 | enable: false 66 | 67 | 68 | #钉钉消息推送配置 69 | #钉钉添加机器人,请查看 70 | #https://github.com/TophantTechnology/ARL/wiki/ARL%202.3%20%E6%96%B0%E6%B7%BB%E5%8A%A0%E5%8A%9F%E8%83%BD%E8%AF%B4%E6%98%8E 71 | DINGDING: 72 | SECRET: "" 73 | ACCESS_TOKEN: "" 74 | 75 | #邮件推送配置 76 | EMAIL: 77 | HOST: "" 78 | PORT: "" 79 | USERNAME: "" 80 | PASSWORD: "" 81 | TO: "" 82 | 83 | # 监控任务 WEBHOOK 配置 84 | # IP/域名监控和站点监控结束后会往下面的URL POST提交JSON数据 85 | # 为了验证身份会在Header中带上Token 字段 86 | # 进入容器测试 docker-compose exec worker bash 87 | # 测试命令 python3.6 -m test.test_webhook 88 | WEBHOOK: 89 | URL: "" 90 | TOKEN: "" 91 | 92 | 93 | #GITHUB 搜索 TOKEN 94 | GITHUB: 95 | TOKEN: "" 96 | 97 | #代理配置项,如"http://127.0.0.1:8080", 端口扫描不会走代理 98 | PROXY: 99 | HTTP_URL: "" 100 | 101 | ARL: 102 | AUTH: true 103 | #API 认证key, 可以访问api/doc查看文档 104 | API_KEY: "" 105 | BLACK_IPS: 106 | - 127.0.0.0/8 107 | - 0.0.0.0/8 108 | - 172.16.0.0/12 109 | - 100.64.0.0/10 110 | #- 10.0.0.0/8 111 | #- 192.168.0.0/16 112 | ## 禁止域名,不可为空数组 113 | FORBIDDEN_DOMAINS: 114 | - edu.cn 115 | - org.cn 116 | - gov.cn 117 | #端口对应前端测试选项 118 | PORT_TOP_10: "80,443,8443,8080,8081,8888,8089,5000,5001,8085,800,81,9000,88,8001,8090" 119 | #域名爆破字典前端大字典选项 120 | DOMAIN_DICT: "/code/app/dicts/domain_2w.txt" 121 | #文件泄漏字典 122 | FILE_LEAK_DICT: "/code/app/dicts/file_top_2000.txt" 123 | 124 | #域名爆破并发数 125 | DOMAIN_BRUTE_CONCURRENT: 300 126 | #组合生成的域名爆破并发数 127 | ALT_DNS_CONCURRENT: 1500 128 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | error_log /var/log/nginx/error.log; 4 | pid /run/nginx.pid; 5 | 6 | # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. 7 | include /usr/share/nginx/modules/*.conf; 8 | 9 | events { 10 | worker_connections 1024; 11 | } 12 | 13 | http { 14 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 15 | '$status $body_bytes_sent "$http_referer" ' 16 | '"$http_user_agent" "$http_x_forwarded_for"'; 17 | 18 | 19 | sendfile on; 20 | tcp_nopush on; 21 | tcp_nodelay on; 22 | keepalive_timeout 65; 23 | types_hash_max_size 2048; 24 | 25 | include /etc/nginx/mime.types; 26 | default_type application/octet-stream; 27 | 28 | # Load modular configuration files from the /etc/nginx/conf.d directory. 29 | # See http://nginx.org/en/docs/ngx_core_module.html#include 30 | # for more information. 31 | include /etc/nginx/conf.d/*.conf; 32 | 33 | server { 34 | listen 80; 35 | server_name _; 36 | 37 | 38 | #access_log logs/host.access.log main; 39 | access_log off; 40 | root /code/frontend; 41 | 42 | location / { 43 | try_files $uri $uri/ /index.html; 44 | index index.html index.htm; 45 | } 46 | 47 | location /api/ { 48 | proxy_set_header Host $http_host; 49 | proxy_pass http://127.0.0.1:5003/api/; 50 | } 51 | 52 | location /swaggerui/ { 53 | proxy_pass http://127.0.0.1:5003/swaggerui/; 54 | } 55 | 56 | } 57 | server { 58 | listen 443 ssl http2; 59 | 60 | ssl_certificate /etc/ssl/certs/arl_web.crt; 61 | ssl_certificate_key /etc/ssl/certs/arl_web.key; 62 | ssl_session_timeout 1d; 63 | ssl_session_cache shared:MozSSL:10m; # about 40000 sessions 64 | ssl_session_tickets off; 65 | 66 | # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam 67 | ssl_dhparam /etc/ssl/certs/dhparam.pem; 68 | 69 | # intermediate configuration 70 | ssl_protocols TLSv1.2 TLSv1.3; 71 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 72 | ssl_prefer_server_ciphers off; 73 | 74 | # HSTS (ngx_http_headers_module is required) (63072000 seconds) 75 | add_header Strict-Transport-Security "max-age=63072000" always; 76 | 77 | access_log off; 78 | root /code/frontend; 79 | 80 | location / { 81 | try_files $uri $uri/ /index.html; 82 | index index.html index.htm; 83 | } 84 | 85 | location /api/ { 86 | proxy_set_header Host $http_host; 87 | proxy_set_header X-Forwarded-Proto $scheme; 88 | proxy_pass http://127.0.0.1:5003/api/; 89 | } 90 | 91 | location /swaggerui/ { 92 | proxy_pass http://127.0.0.1:5003/swaggerui/; 93 | } 94 | 95 | error_page 497 https://$http_host; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Author: moshang 3 | Blog: blog.tyslz.cn 4 | ''' 5 | import sys 6 | import json 7 | import requests 8 | 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | 12 | def login(url, username, password): 13 | login_url = f"{url}/api/user/login" 14 | login_data = json.dumps({"username": username, "password": password}) 15 | headers = { 16 | "Accept": "application/json, text/plain, */*", 17 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", 18 | "Content-Type": "application/json; charset=UTF-8" 19 | } 20 | response = requests.post(login_url, data=login_data, headers=headers, verify=False) 21 | if response.status_code == 200: 22 | token = response.json().get('data', {}).get('token') 23 | if token: 24 | print("[+] Login Success!!") 25 | return token 26 | print("[-] Login Failure!") 27 | return None 28 | 29 | 30 | def add_finger(name, rule, url, token): 31 | add_url = f"{url}/api/fingerprint/" 32 | headers = { 33 | "Accept": "application/json, text/plain, */*", 34 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", 35 | "token": token, 36 | "Content-Type": "application/json; charset=UTF-8" 37 | } 38 | data = {"name": name, "human_rule": rule} 39 | response = requests.post(add_url, data=json.dumps(data), headers=headers, verify=False) 40 | if response.status_code == 200: 41 | print(f"Add: [\033[32;1m+\033[0m] {json.dumps(data)}\nRsp: [\033[32;1m+\033[0m] {response.text}") 42 | else: 43 | print(f"[-] Failed to add fingerprint for {name}") 44 | 45 | 46 | def upload_finger_file(file_path, url, token): 47 | upload_url = f"{url}/api/fingerprint/upload/" 48 | headers = { 49 | "Accept": "application/json, text/plain, */*", 50 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", 51 | "token": token, 52 | } 53 | files = {'file': ('finger.json', open(file_path, 'rb'), 'application/json')} 54 | response = requests.post(upload_url, files=files, headers=headers, verify=False) 55 | if response.status_code == 200: 56 | print(f"[+] File upload success: {file_path}\nRsp: [\033[32;1m+\033[0m] {response.text}") 57 | else: 58 | print(f"[-] Failed to upload file: {file_path}\nStatus Code: {response.status_code}\nResponse: {response.text}") 59 | 60 | 61 | def main(url, token, method, file_path=None): 62 | if method == "new": 63 | if not file_path: 64 | file_path = "./finger.json" 65 | upload_finger_file(file_path, url, token) 66 | elif method == "old": 67 | if not file_path: 68 | file_path = "./finger.json" 69 | 70 | with open(file_path, 'r', encoding="utf-8") as f: 71 | load_dict = json.loads(f.read()) 72 | 73 | body_template = "body=\"{}\"" 74 | title_template = "title=\"{}\"" 75 | hash_template = "icon_hash=\"{}\"" 76 | 77 | for i in load_dict['fingerprint']: 78 | finger_json = i 79 | 80 | name = finger_json['cms'] 81 | method = finger_json['method'] 82 | location = finger_json['location'] 83 | 84 | if method == "keyword": 85 | if "body" in location: 86 | template = body_template 87 | elif "title" in location: 88 | template = title_template 89 | else: 90 | template = hash_template 91 | 92 | if len(finger_json['keyword']) > 0: 93 | for rule in finger_json['keyword']: 94 | formatted_rule = template.format(rule) 95 | add_finger(name, formatted_rule, url, token) 96 | else: 97 | formatted_rule = template.format(finger_json['keyword'][0]) 98 | add_finger(name, formatted_rule, url, token) 99 | else: 100 | print("[-] Invalid method. Use 'new' or 'old'.") 101 | 102 | 103 | if __name__ == '__main__': 104 | if len(sys.argv) == 5 or len(sys.argv) == 6: 105 | login_url = sys.argv[1] 106 | login_name = sys.argv[2] 107 | login_password = sys.argv[3] 108 | method = sys.argv[4] 109 | file_path = sys.argv[5] if len(sys.argv) == 6 else None 110 | 111 | token = login(login_url, login_name, login_password) 112 | if token: 113 | main(login_url, token, method, file_path) 114 | else: 115 | print(''' 116 | usage: 117 | python3 ADD-ARL-Finger.py https://127.0.0.1:5003/ admin password old [file_path] 使用旧方式添加指纹 118 | 指纹文件格式 119 | { 120 | "cms": "致远OA", 121 | "method": "keyword", 122 | "location": "rule: body", 123 | "keyword": [ 124 | "/seeyon/USER-DATA/IMAGES/LOGIN/login.gif" 125 | ] 126 | } 127 | python3 ADD-ARL-Finger.py https://127.0.0.1:5003/ admin password new [file_path] 使用新方式上传指纹文件 128 | 指纹文件格式为: 129 | - name: 致远OA 130 | rule: body="/seeyon/USER-DATA/IMAGES/LOGIN/login.gif" 131 | 132 | ''') --------------------------------------------------------------------------------