目录

  1. 1. 前言
  2. 2. 任务一:网络拓扑构建
  3. 3. 任务二:交换机配置(Unsolved)
  4. 4. 任务十三:代码安全审计
  5. 5. 渗透-数字化装配场景

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

2024工业互联网安全

2024/11/28 线下赛
  |     |   总文章阅读量:

前言

赛制是 理论 + 工控ctf + 渗透or运维build

理论赛本地大模型魅力时刻

ctf 部分上来先考计算机网络/路由交换实践,而我还在重修计网,令人感叹(

然后用 ctf 的分换取渗透/build启动环境的机会,渗透每次启动环境都只有一小时的时间能打,只能说根本打不完555

image-20241128171247204


设备如下,是西门子的,交换机是tplink的,比赛的时候显示器接的是 SCADA:

1733190508111

任务一:网络拓扑构建

以本机能够ping通192.168.N.86成功为标准构建拓扑(N是自己队伍的C段)

安装所给的console驱动

笔记本 usb 插 console 线,另一头插交换机的 console 口进行连接,xshell用 SERIAL 协议连接 COM7 口(在设备管理器-端口处查看对应的端口)

image-20241203141901391

波特率设置为主办方提供的38400,设备管理器那边也要设置为38400(波特率不对会没有输入输出)

image-20241203142021367

然后就能连上

拓扑图:

image-20241128120244671

console连上交换机2进行配置,键入?可以查看可用命令和帮助,tab可以补全,删除字符要用del:

TL-SG5210>
 broadcast            - Write message to all users logged in,at most 256
                         characters
 enable               - Enter Privileged EXEC Mode
 ping                 - Ping command
 tracert              - Tracet route to destination
 exit                 - Exit current mode

TL-SG5210>enable

TL-SG5210#
 broadcast            - Write message to all users logged in,at most 256
                         characters
 configure            - Enter Global Configuration Mode
 copy                 - Config file commands
 debug                - Debugging commands
 enable-admin         - Achieve the admin privilege
 firmware             - Firmware commands
 logout               - Logout the system
 ping                 - Ping command
 reboot               - Reboot the system
 remove               - Config file commands
 reset                - Reset the system
 tracert              - Tracet route to destination
 clear                - Reset functions
 exit                 - Exit current mode
 show                 - Display system information
 
TL-SG5210#ping 192.168.52.86

Pinging 192.168.52.86 with 64 bytes of data :
Destination Host Unreachable!
Destination Host Unreachable!
Destination Host Unreachable!
Destination Host Unreachable!

Ping statistics for 192.168.52.86:
    Packets: Sent = 0 , Received = 0 , Lost = 0 (0% loss)
Approximate round trip times in milli-seconds:
Minimum = 0ms , Maximum = 0ms , Average = 0ms

TL-SG5210#enable-admin
Authentication fail. You should enable AAA first.

Error password!

TL-SG5210#configure

TL-SG5210(config)#
 aaa                  - Authentication Authorization Accounting commands
 access-list          - Create a temporary Access-List entry
 arp                  - Address Resolution Protocol (ARP)
 boot                 - Boot configuration
 cloud-server         - Config Cloud Server status
 contact-info         - Contact information
 dns                  - Configure domain name system
 dot1q-tunnel         - Dot1q-tunnel configuration
 dot1x                - Globally Enable 802.1x feature
 enable               - Configure enable password
 gvrp                 - GVRP global configuration
 holiday              - Configure the holiday time range
 hostname             - System name
 interface            - Select an interface to configure
 ip                   - IP address commands
 ipv6                 - Internet Protocol version 6 (IPv6)
 lacp                 - Configure LACP global system priority
 line                 - Enter Line Configuration Mode
 lldp                 - Configure LLDP global subcommands
 location             - System location
 logging              - System log configuration
 loopback-detection   - Enable loopback-detection
 mac                  - Global Bridge table configuration commands
 mac-vlan             - Configure a MAC-based VLAN entry
 monitor              - Monitoring different system events
 port-channel         - EtherChannel configuration
 protocol-vlan        - Protocol VLAN commands
 qos                  - Configure quality of service (QoS) on the device
 radius-server        - Modify RADIUS query parameters
 rmon                 - SNMP RMON configuration
 router               - router
 service              - Change Service state
 snmp-server          - SNMP server configuration
 spanning-tree        - Configure Spanning Tree Subsystem
 system-time          - System-time configuration
 tacacs-server        - Modify TACACS query parameters
 telnet               - Telnet configuration
 time-range           - Configure the time range 
 ucl                  - UCL commands
 user                 - Add a new user or modify an exist user
 vlan                 - VLAN commands
 voice                - Voice VLAN global commands
 clear                - Reset functions
 end                  - Return to Privileged EXEC Mode
 exit                 - Exit current mode
 no                   - Negate command
 show                 - Display system information
 
TL-SG5210(config)#interface 
 fastEthernet         - FastEthernet IEEE 802.3
 gigabitEthernet      - GigabitEthernet IEEE 802.3z
 loopback             - Loopback interface
 port-channel         - Ethernet Channel of interfaces
 range                - Interface Range Mode
 ten-gigabitEthernet  - Ten-GigabitEthernet IEEE 802.3ae
 vlan                 - Interface VLAN Mode

那么就是要在config模式下对交换机路由进行配置,测试发现interface gigabitEthernet 1/0/2进入 config-if 模式下配不了 ip 地址

先看一下现有的路由配置

TL-SG5210#show 
 aaa                  - Display the AAA configuration
 access-list          - Display ACL information
 arp                  - Display ARP information
 bandwidth            - Display bandwidth rate configuration
 boot                 - Display the boot configuration
 cable-diagnostics    - Display Cable diagnostics results
 cloud-server         - cloud-server 
 cluster              - Display cluster information
 cpu-utilization      - Display CPU utilization
 debugging            - Dispaly modules debug status
 dot1q-tunnel         - Display VLAN VPN configuration
 dot1x                - Display 802.1x configuration information
 etherchannel         - Display EtherChannel information
 gvrp                 - Display GVRP information
 history              - Display command history
 holiday              - Display holiday information
 image-info           - Display the image info
 interface            - Display interface status and configuration
 ip                   - Display IP information
 ipv6                 - Internet Protocol version 6 (IPv6)
 lacp                 - Display LACP configuration
 lldp                 - Display LLDP information
 logging              - Display log information
 loopback-detection   - Display loopback detection information
 mac                  - Display mac information
 mac-vlan             - Display MAC Based VLAN information
 memory-utilization   - Display memory utilization
 monitor              - Display monitor sessions information
 port                 - Display Ethernet port configuration
 protocol-vlan        - Display protocol VLAN configuration
 qos                  - Display QoS related information
 radius-server        - Show radius server information
 rmon                 - Display SNMP RMON information
 running-config       - Display running config
 snmp-server          - Display SNMP related information
 spanning-tree        - Display spanning tree information
 startup-config       - Display startup config
 storm-control        - Display interface's storm-control configuration
 system-info          - Display system information
 system-time          - Display current system time information
 tacacs-server        - Show tacacs+ server information
 telnet-status        - Display telnet status
 time-range           - Display time segment information
 ucl                  - Display UCL information
 user                 - Display user account information
 vlan                 - Display VLAN information
 voice                - Display voice VLAN configuration

TL-SG5210#show ip interface 
 brief                - Brief summary of IP status and configuration
 fastEthernet         - FastEthernet IEEE 802.3
 gigabitEthernet      - GigabitEthernet IEEE 802.3z
 loopback             - Loopback interface
 port-channel         - Ethernet Channel of interfaces
 ten-gigabitEthernet  - Ten-GigabitEthernet IEEE 802.3ae
 vlan                 - VLAN interface
 <cr>

TL-SG5210#show ip interface brief 
 Interface             IP-Address          Method  Status  Protocol  Shutdown
 ---------             ----------          ------  ------  --------  --------
 Vlan1                 192.168.0.1/24      Static  up      up        no

config 模式下启用AAA

TL-SG5210(config)#aaa 
 accounting           - Configure accounting method list
 authentication       - Configure authentication method list
 enable               - Enable AAA
 group                - Configure AAA group

TL-SG5210(config)#aaa enable

TL-SG5210(config)#exit

TL-SG5210#enable-admin

接下来结合这张表配置 vlan

image-20241203143852945

TL-SG5210(config)#interface 
 fastEthernet         - FastEthernet IEEE 802.3
 gigabitEthernet      - GigabitEthernet IEEE 802.3z
 loopback             - Loopback interface
 port-channel         - Ethernet Channel of interfaces
 range                - Interface Range Mode
 ten-gigabitEthernet  - Ten-GigabitEthernet IEEE 802.3ae
 vlan                 - Interface VLAN Mode

TL-SG5210(config)#interface vlan                                               
 <1-4094>             - VLAN interface number
 
TL-SG5210(config)#interface vlan 4

TL-SG5210(config-if)#
 access-list          - Configure the access list control module
 arp                  - Address Resolution Protocol (ARP)
 description          - Specify vlan interface description
 ip                   - Configure interface internet protocol
 ipv6                 - IPv6 interface subcommands
 shutdown             - Shutdown the selected interface
 clear                - Reset functions
 end                  - Return to Privileged EXEC Mode
 exit                 - Exit current mode
 no                   - Negate command
 show                 - Display system information
 
TL-SG5210(config-if)#ip 
 address              - Set IP address manually
 address-alloc        - Set IP address by bootp or dhcp
 helper-address       - Specify a destination address for DHCP broadcasts
 local-proxy-arp      - Specify local proxy ARP configuration
 proxy-arp            - Specify proxy ARP configuration
 rip                  - Specify RIP protocol interface configuration

TL-SG5210(config-if)#ip address
address             address-alloc       

TL-SG5210(config-if)#ip address 192.168.52.86 
 A.B.C.D              - Specify the IP subnet mask

TL-SG5210(config-if)#ip address 192.168.52.86 255.255.255.0

TL-SG5210(config-if)#no shutdown 
shutdown            

这样就配置完成了,然后把电脑的网线另一头从hub上拔下来接到交换机的 lan 口上就能ping通了


任务二:交换机配置(Unsolved)

对竞赛平台中的网络设备进行配置,完成该任务应能够实现以下效果:
1. 工业防火墙、工业日志审计的管理口同处于交换机 SW1 的 VLAN 5 下,操作人员可通过桌面 Hub 访问 MES 的 Web 页面,通过 G1/0/3 访问工业日志审计管理页面、工业防火墙管理页面。
2. SCADA、HMI 同处于 VLAN 下,操作人员可通过 SCADA 访问 MES 的 web 页面。
3. 通过工业日志审计能够监控流经交换机 SW1 和交换机 SW2 的所有流量数据。
4. 将交换机 1 的 G1/0/8 的 IP 地址配置为 12.0.N.254/24。
5. 添加静态路由 171.16.0.0/16,网关为 12.0.N.1

直接把console线插到交换机1上,用同样的方式配置 vlan5,使其能够通过网线访问工业防火墙、工业日志审计

image-20241128150333714

image-20241128150413106

但是还差一个 通过桌面 Hub 访问 MES 的 Web 页面

hint:MES的web服务开在不常见端口

于是把65535全扫了一遍。。然后没扫到,不知道怎么搞的


任务十三:代码安全审计

对以下这段代码进行安全审计:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $url = isset($_POST['url']) ? trim($_POST['url']) : '';
    if (filter_var($url, FILTER_VALIDATE_URL) === false) {
        $error = "无效的 URL,请检查格式。";
    } else {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
        $response = curl_exec($ch);
        
        if ($response === false) {
            $error = 'cURL 错误: ' . curl_error($ch);
        }
        curl_close($ch);
    }
}
?>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>危化品处理系统</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        .container {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            width: 400px;
        }
        h1 {
            text-align: center;
        }
        label {
            font-size: 14px;
            margin-bottom: 8px;
            display: block;
        }
        input[type="text"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 15px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }
        .btn {
            width: 100%;
            padding: 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        .btn:hover {
            background-color: #45a049;
        }
        .error {
            color: red;
            font-size: 14px;
        }
        .result {
            margin-top: 20px;
            background-color: #f1f1f1;
            padding: 10px;
            border-radius: 4px;
            word-wrap: break-word;
        }
    </style>
</head>
<body>

<div class="container">
    <h1>危化品处理</h1>
    <form method="POST" action="">
        <label for="url">请输入 URL:</label>
        <input type="text" id="url" name="url" placeholder="请输入有效的 URL" value="<?php echo isset($url) ? htmlspecialchars($url) : ''; ?>" required>
        
        <button type="submit" class="btn">提交处理</button>

        <!-- 显示错误消息 -->
        <?php if (isset($error)): ?>
            <p class="error"><?php echo htmlspecialchars($error); ?></p>
        <?php endif; ?>
    </form>

    <?php if (isset($response) && !isset($error)): ?>
        <div class="result">
            <h3>请求结果:</h3>
            <pre><?php echo htmlspecialchars($response); ?></pre>
        </div>
    <?php endif; ?>
</div>

</body>
</html>
  1. 指出代码存在漏洞的行数:一眼在12行$response = curl_exec($ch);这里可以随便ssrf

  2. 如果要对代码进行修复,应该做出怎样修改:简单过滤一手

    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $url = isset($_POST['url']) ? trim($_POST['url']) : '';
        if (filter_var($url, FILTER_VALIDATE_URL) === false || !preg_match('/^(https?|ftp):\/\/.[^\s]*$/', $url)) {
            $error = "无效的 URL,请检查格式。";
        } else {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
            curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
            $response = curl_exec($ch);
            
            if ($response === false) {
                $error = 'cURL 错误: ' . curl_error($ch);
            }
            curl_close($ch);
        }
    }
    ?>

渗透-数字化装配场景

日志处存在任意文件读取,甚至可以列出目录

image-20241128140416081

../../../../proc/self/cmdline

image-20241128140838919

读取的日志有最大长度

../data_monitor.py

"""
opc-ua类: 实现工业机器交互
opc-ua安装使用方式:pip install opcua
opencv-python
"""
import os
import tempfile
import threading
import time
import datetime
import json
from functools import wraps
from itertools import islice

from flask import Flask, send_file, request, Response, make_response
from flask_cors import CORS
from opcua import Client, ua, Node
import cv2
from base_info import sub_ids, ip, port, host, user, pw, database
from get_generate_plan import get_random_plan
from update_value import monitor_task
from mysql_db import MySQLDatabase


app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})

abs_path = os.path.dirname(os.path.abspath(__file__))
print(abs_path)


@app.route("/dashboard")
def get_monitor():
    try:
        with MySQLDatabase(host, user, pw, database) as db:
            data = db.execute_query("SELECT name_key, content FROM monitor",)
            info = dict(zip([x[0] for x in data], [x[1] for x in data]))
        return {'code': 200, 'data': info}

    except Exception as e:
        return {'code': 500, 'data': str(e)}


@app.route("/plan")
def get_plan():
    try:
        plan_list = get_random_plan()
        return {'code': 200, 'data': plan_list}

    except Exception as e:
        return {'code': 500, 'data': str(e)}


@app.route("/get_log", methods=["POST"])
def get_log():
    filename = request.json.get("name")

    file_path = f"{abs_path}/log/{filename}"
    if not os.path.isdir(file_path):
        if os.path.exists(file_path):
            with open(file_path, 'r', encoding='utf-8') as f:
                lines = list(islice(f, 100))

            with tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8') as temp_file:
                temp_file.writelines(lines)
                temp_file_path = temp_file.name

            response = make_response(send_file(temp_file_path))
            response.headers['Content-Type'] = 'application/json'
            return response
        else:
            return "文件不存在", 400
    else:
        return Response(
            json.dumps(os.listdir(f"{abs_path}/{filename}")),
            content_type="application/json",
            headers=[("Content-Type", "application/json")]
        )


class VideoCamera(object):
    def __init__(self, host="", account="", passwd=""):
        self.url = f"rtsp://{account}:{passwd}@{host}:554/Streaming/Channels/1"
        self.video = cv2.VideoCapture(self.url)

    @property
    def check(self):
        if not self.video.isOpened():
            return False
        return True

    def __del__(self):
        self.video.release()

    def get_frame(self):
        success, image = self.video.read()
        ret, jpeg = cv2.imencode('.jpg', image, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
        return jpeg.tobytes()

../mysql_db.py

import mysql.connector


class MySQLDatabase:
    def __init__(self, host, user, password, database):
        self.host = host
        self.user = user
        self.password = password
        self.database = database
        self.conn = None
        self.cursor = None

    def __enter__(self):
        self.conn = mysql.connector.connect(
            host=self.host,
            user=self.user,
            password=self.password,
            database=self.database
        )
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()

    def execute_query(self, query, params=()):
        self.cursor.execute(query, params)
        result = self.cursor.fetchall()
        return result

    def update_record(self, query, params=()):
        self.cursor.execute(query, params)
        self.conn.commit()
        return self.cursor.lastrowid


""" 使用with语句进行数据库操作
with MySQLDatabase(host='localhost', user='yourusername', password='yourpassword', database='mydatabase') as db:
    result = db.execute_query("SELECT * FROM customers")
    for x in result:
        print(x)

    updated_rows = db.update_record("UPDATE customers SET address = 'Canyon 123' WHERE address = 'Valley 345'")
    print(updated_rows, "记录被修改")
"""

../base_info.py

"""
sis监控文件统一放到  /home/opt/monitor目录下
mis前端文件统一放到  /var/www/html 并指定默认访问页面为index.php
"""

# mysql
host = "127.0.0.1"
user = "root"
pw = "abc123!@#"
database = "mesdb"

# 组态系统信息
ip = '192.168.2.32'
port = 4862

# 监控节点数据
sub_ids = (
    'ns=1;s=t|DLSSL',  # 打螺丝数量
    'ns=1;s=t|FTQS',  # 发条圈数
    'ns=1;s=t|FZJD',  # 翻转角度
    'ns=1;s=t|FZZP_Run',  # 翻转装配运行
    # 'ns=1;s=t|FZZP_Start',  # 翻转装配启动
    # 'ns=1;s=t|FZZP_Stop',  # 翻转装配停止
    # 'ns=1;s=t|hand',  # 手自动模式
    'ns=1;s=t|HJ_SD',  # 环境湿度
    'ns=1;s=t|HJ_WD',  # 环境温度
    'ns=1;s=t|JGDB_Run',  # 激光打标运行
    # 'ns=1;s=t|JGDB_Start',  # 激光打标启动
    # 'ns=1;s=t|JGDB_Stop',  # 激光打标停止
    'ns=1;s=t|JGPL',  # 激光频率
    'ns=1;s=t|JXSL_Run',  # 机芯上料运行
    # 'ns=1;s=t|JXSL_Start',  # 机芯上料启动
    # 'ns=1;s=t|JXSL_Stop',  # 机芯上料停止
    'ns=1;s=t|JZDL_Run',  # 基座打料运行
    # 'ns=1;s=t|JZDL_Start',  # 基座打料启动
    # 'ns=1;s=t|JZDL_Stop',  # 基座打料停止
    'ns=1;s=t|SCSL',  # 生产数量
    'ns=1;s=t|SGDLS_Run',  # 上盖打螺丝运行
    # 'ns=1;s=t|SGDLS_Start',  # 上盖打螺丝启动
    # 'ns=1;s=t|SGDLS_Stop',  # 上盖打螺丝停止
    'ns=1;s=t|SJSFT_Run',  # 视觉上发条运行
    # 'ns=1;s=t|SJSFT_Start',  # 视觉上发条启动
    # 'ns=1;s=t|SJSFT_Stop',  # 视觉上发条停止
    'ns=1;s=t|SLSD',  # 上料速度
    'ns=1;s=t|tongxun_state',  # PLC通讯状态
    'ns=1;s=t|TPZY_Run',  # 托盘转运运行
    # 'ns=1;s=t|TPZY_Start',  # 托盘转运启动
    # 'ns=1;s=t|TPZY_Stop',  # 托盘转运停止
    'ns=1;s=t|YJ_Run',  # 一键运行
    # 'ns=1;s=t|YJ_Start',  # 一键启动
    # 'ns=1;s=t|YJ_Stop',  # 一键停止
    'ns=1;s=t|ZN_M_OVERSPEED',  # 电机超速报警
)

../update_value.py

import re
import os
import time
import datetime

from base_info import sub_ids, host, user, pw, database
from mysql_db import MySQLDatabase


node_dict = {x: re.search(r'\|(.*)', x).group(1) for x in sub_ids}
abs_path = os.path.dirname(os.path.abspath(__file__))


def monitor_task(key, *args):
    start_time = time.time()

    try:
        with MySQLDatabase(host, user, pw, database) as db:
            result = db.execute_query(
                "SELECT * FROM monitor WHERE name= %s", (key,))
            if result:
                db.update_record(
                    "UPDATE monitor SET content=%s WHERE name = %s", (str(*args), key))
            else:
                db.update_record(
                    "INSERT INTO monitor (name, name_key, content) VALUES (%s, %s, %s)",
                    (key, node_dict.get(key), str(*args)))

            print(f">>>>> {node_dict.get(key)} 记录被修改 >>>>>")

    except Exception as e:
        print(f"monitor_task_error: {str(e)}")

    finally:
        print(f"monitor_task_use_time: {time.time() - start_time}\n")

../get_generate_plan.py

import random
from datetime import datetime, timedelta


NUM_PLANS = 20
MIN_PLAN_QUANTITY = 10
MAX_PLAN_QUANTITY = 100
MIN_PLAN_DURATION = 1
MAX_PLAN_DURATION = 14


def generate_plan(start_date, end_date, i):
    order_number = generate_plan_number(start_date, i)
    plan_quantity = random.randint(MIN_PLAN_QUANTITY, MAX_PLAN_QUANTITY)
    plan_start = random_date(start_date, end_date).strftime("%Y-%m-%d")
    plan_duration = random.randint(MIN_PLAN_DURATION, MAX_PLAN_DURATION)
    plan_end = (start_date + timedelta(days=plan_duration)).strftime("%Y-%m-%d")
    order_status = random.choice(["正在进行", "已完成", "取消"])
    completion_rate = round(random.uniform(0, 1) * 100, 2)

    return {
        "order_number": order_number,
        "plan_quantity": plan_quantity,
        "plan_start": plan_start,
        "plan_end": plan_end,
        "order_status": order_status,
        "completion_rate": completion_rate
    }


def generate_plan_number(start_date, i):
    return f"ORDER-{start_date.strftime('%Y%m%d')}{str(i+1).zfill(2)}"


def random_date(start_date, end_date):
    time_between = end_date - start_date
    random_days = random.randint(0, time_between.days)
    return start_date + timedelta(days=random_days)


def production_plan(start_date, end_date, num_plans):
    return [generate_plan(start_date, end_date, i) for i in range(num_plans)]


def get_random_plan():
    start_date = datetime.now().date()
    end_date = start_date + timedelta(days=30)
    random.seed(42)
    return production_plan(start_date, end_date, NUM_PLANS)

../../../../../home/scada/app.py,是5001端口上的服务

from flask import Flask, render_template, request, redirect, url_for, session, render_template_string,Markup,make_response,flash
import random
import hashlib
from flask_sqlalchemy import SQLAlchemy  
from werkzeug.security import generate_password_hash, check_password_hash  
import pymysql
from jinja2 import Template
# flag{Yes_You_find_Me!}
app = Flask(__name__)
app.secret_key = 'icssla'  # 设置一个密钥用于session加密
# 数据库连接配置  
db_config = {  
    'host': 'localhost',  
    'user': 'root',  
    'password': 'abc123!@#',  
    'db': 'flask_login_db',  
    'charset': 'utf8mb4',  
    'cursorclass': pymysql.cursors.DictCursor,  
} 
# 设置 session cookie 的 HttpOnly 和 Secure 标志
app.config['SESSION_COOKIE_HTTPONLY'] = False  # 设置为 False,允许 JavaScript 访问
app.config['SESSION_COOKIE_SECURE'] = False  # 设置为 False,允许在 HTTP 上发送 cookie

def contains_sql_keywords(input_string, keywords): 
    for keyword in keywords:  
        if keyword in input_string:  
            return True  
    return False  

def contains_ssti_keywords(input_string, keywords): 
    for keyword in keywords:  
        if keyword in input_string:  
            return True  
    return False  
# 假设这是你的用户数据库
#users = {
#    "admin": "admin@123"
#}
md5_hash = hashlib.md5() 
# 生成随机风机数据
def generate_random_data(page, num_entries=100):
    data = []
    for i in range(num_entries):
        fan = f"Fan {i+1}"
        speed = random.randint(1000, 3000)  # 转速范围 1000-3000 RPM
        power = random.uniform(100.0, 500.0)  # 发电量范围 100-500 kW
        data.append({"fan": fan, "speed": speed, "power": power})
    page_size = 10  # 每页显示的数据条数
    try:
        page = int(page)
        start_index = (page - 1) * page_size
        end_index = start_index + page_size
        return data[start_index:end_index], page
    except ValueError:
        return [], 1  # 如果输入的不是有效的整数,返回空数据和默认页数1

@app.route('/')
def index():
    if 'username' in session:
        return render_template('dashboard.html')
    return redirect(url_for('login'))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        sql_keywords = ['select', 'from', 'where', 'union', 'join', 'sleep',' ']  
        input_string = f"{username}:{password}"  
        md5_hash.update(input_string.encode('utf-8'))
        hex_dig = md5_hash.hexdigest()
        connection = pymysql.connect(**db_config)  
        if contains_sql_keywords(username, sql_keywords):  
            return "Error: There are illegal characters in the character you entered.", 400
        try:  
            with connection.cursor() as cursor:  
                # 执行查询  
                sql = "SELECT * FROM users WHERE username ='" + username +"'"
                cursor.execute(sql)  
                #sql = "SELECT * FROM users WHERE username = %s"  
                #cursor.execute(sql, (username,))  
                result = cursor.fetchone()  
                # 验证用户名和密码  
                if result and result['password'] == password:   
                    flash('Login successful!', 'success')  
                    session['username'] = username  
                    # 假设 session_token 是与 session 相关的一个值  
                    session_token = str(random.randint(100000, 999999))  # 仅为示例  
                    response = make_response(redirect(url_for('index')))  
                    response.set_cookie('username', hex_dig, httponly=False, secure=False)  
                    return response  
                else:  
                    flash('Invalid username or password', 'danger')
                    return 'Invalid credentials'
        finally:  
            connection.close()  
    return render_template('login.html')

@app.route('/logout')
def logout():

最后在root下翻出一个flag,home下好像也有一个flag,交了俩flag

接下来应该是拿shell然后打横向,但是时间太短反应不过来(