Automating Infrastructure Scanning: Script 3.0 for Scanner-BC 7

Hello, tekkix! This is Anton Dyatlov, information security engineer at Selectel. Recently, Echelon released Scanner-BC 7, which changed the API logic. The old script stopped working properly, and improvements were naturally needed.

Let’s take a look at the main changes and improvements. Below, I will provide code snippets and explain what exactly has changed and how it works.

If you don’t know what Scanner-VC is, here’s a brief explanation:

Scanner-VC 7 is a tool for analyzing network protection, which scans ports on IPs and subnets to identify gaps and vulnerabilities in the system. It is deployed within the local network of an organization. Thanks to the availability of an API, the scanner’s work can be well automated—scripts can be prepared that will run checks on schedule, collect vulnerabilities, and generate reports.

In the new version of the script for Scanner-VC 7, the key ideas of previous solutions have been preserved, but the pipeline has been significantly simplified, and new API capabilities have been integrated.

The script fully automates the entire cycle, from scanning the network and finding vulnerabilities to generating reports and sending alerts via messenger. I minimize any manual intervention so that the automation is truly complete and even autonomous.

Earlier, using the example of the sixth version of the scanner, I showed how the scanner’s API works and how to automate the typical process—create a task for different spaces, start and track the network scan and vulnerability scan, and eventually receive reports with alerts.

But everything strives for simplification and harmony. The desire to simplify the transition between stages, provide more flexible processing of input data, and increase the process's stability kept me awake at night. As a result, new features were added in version 3.0, and existing mechanisms were improved.

New Requirements and Capabilities

In version 3.0, I aimed to make the script more universal and reliable. Let’s take a look at the main tasks that were addressed in version 2.0.

Finally implement a flexible input data format. Support is provided for working with individual IP addresses and subnets. If a subnet is specified in JSON, the script automatically “pokes” all addresses in that subnet and adds live hosts for scanning.

Grouping by clients and spaces for internal infrastructure. All addresses, whether a subnet or a single IP, are grouped by the name attribute into one group. Each group corresponds to one netscan, one vulnscan, and one report. This reduces the load on the scanner, as instead of dozens of tasks for individual IPs—which immediately causes web hanging—one task is created for the entire group.

Step-by-step pipeline with state preservation. The script continues to use a JSON file to store progress, but now the transition between stages is fully automated. At the beginning of each run, the stage field in JSON is checked: netscan, vulnscan, or report. Depending on this, the required stage is launched.

After completing the current stage, we save the new stage in JSON and exit. This way, the script can be set to auto-run in the cron scheduler as often as needed, and work will continue from where it stopped, instead of creating a netscan for all your assets every time.

New challenges addressed by v3.0

Auto-update and authorization. As before, an external script is used with curl for authorization and obtaining cookies. But now, after each request to the API, we check the response code: if it is 401 or 403 (main thing is not 404), the script automatically re-authorizes. This eliminates situations where the token expired and the script crashed, which happened quite often.

Error handling and recovery. A recreate_task_on_error function has appeared. If during task status checking we see that it is in error, failed, or not_found state, the script itself deletes the failed task and creates a new one with the same parameters. Here is a code example:

 def recreate_task_on_error(
    auth_cookies, task_type, client_name, old_task_id, create_func, create_args
):
    print(f"Task {old_task_id} is in error. Recreating...")

    delete_task(auth_cookies, old_task_id)

    new_task_id = create_func(auth_cookies, client_name, *create_args)
    if new_task_id:
        run_task(auth_cookies, new_task_id)
        print(f"Task recreated. New ID: {new_task_id}")
        return new_task_id

    print("Failed to recreate the task.")
    return None

This provides self-recovery: we no longer need to manually clean and restart tasks. Before, if something went wrong, we had to manually remove the task because the entire task pool couldn't proceed further due to it.

This provides self-recovery: we no longer need to manually clean and restart tasks. Before, if something went wrong, we had to manually remove the task because the entire task pool couldn't proceed further due to it.

Refusal of direct work with the database. In version 2.0, SQL commands were needed to clean "stuck" records in the database, as advised by support. Now this is no longer needed: the script, as before, deletes created assets and tasks via the API when the job is completed.

As a result, version 3.0 performs all the same actions, but with improved user experience, including new features: more reliable code, automatic task checking, and flexibility in input data. Thanks for the feedback to Ivan @is113 and Ruslan @ru_secops!

Flexible IP and network input

As in 2.0, in the JSON list of clients, you can specify either the ip field or the net field. But now the script first detects active addresses in subnets and then groups them. At the beginning of execution, if stage = netscan, the script iterates through all records with the net key, pings them, and creates new entries for "live" IPs.

def get_alive_ips(network: str):
    net = ipaddress.ip_network(network, strict=False)
    alive = []
    for ip in net.hosts():
        ip_str = str(ip)
        try:
            result = subprocess.run(
                ["ping", "-c", "1", "-W", "1", ip_str],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            if result.returncode == 0:
                alive.append(ip_str)
        except Exception:
            continue
    return alive

]
[
alive_ips = get_alive_ips(net)
            print(f"In network {net} for client {name}, found {len(alive_ips)} alive IPs")

The function get_alive_ips(network) pings each address in the network in a loop. As a result, all working addresses from the subnet are added as separate elements to the client list. For automation, this is an excellent solution: the script itself identifies and adds all alive hosts, since you cannot add an entire subnet via the API.

Grouping by names

Then we combine addresses by clients with the same name field. As a result, a group is formed for each unique field:

def group_clients_by_name(clients_list):
        groups = {}
        for c in clients_list:
            name = c.get("name")
            if not name:
                continue
            if name not in groups:
                groups[name] = {
                    "name": name,
                    "ips": [],
                    "assets": [],
                    "id_netscan": None,
                    "id_vulnscan": None,
                    "status": None,
                }

In version 3.0, we store not only the IP list for each group but also task IDs and status. This, as before, helps track progress, but now, with the introduced error handler, if there is a task with an error in a group, it restarts automatically.

New code structure and configuration

Some other changes in version 3.0 relate to code organization and launch parameters.

Authorization has been moved to a separate curl script, in the main code subprocess.run(["bash", CURL_FILE]) is executed, and then the cookie file is read. The external module in git is the base. Additionally, variables for Telegram: TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID — are now read from the environment, which is very secure.

Separately about Telegram. In the previous article, I mentioned that I would explore the possibility of exporting reports to the messenger to instantly download reports without any issues. However, during the implementation process, I consciously abandoned this idea. Sending full vulnerability reports through third-party platforms is an unjustifiable risk, and security must still be maintained. As a result, I left only operational alerts in Telegram, while the data should remain within a protected environment.

Results of improvements v3.0

The new version of the script is the result of error corrections, accumulated experience, and adaptation to the innovations of Scanner-VS 7. Some key advantages of the new code include several points.

Input flexibility has appeared. We simply specify the network or IP, and the script will check the working hosts in the subnets. Now, minimal manual work is required: one JSON file with the list of addresses and cron for launching — that’s all that’s needed for automation.

Reliability has increased. The script checks statuses, re-logs in, recreates failed tasks, and even if the machine with the scanner reboots, after recovery, it will continue from where it left off.

Ease of use. The list of addresses with names allows optimizing the scanner's work, avoiding overloading the web, and managing a large number of clients or internal spaces.

Reports are almost untouched — the only change for greater security was the anonymization of variables with tokens and chat IP. In the end, the script improvements turned out to be useful and tested in live scanning: erroneous tasks were recreated, each launch always triggered a new authorization, and 4XX errors didn’t appear in the CLI. I hope this article will help you catch the tailwind in the direction of automating your scans.

Write in the comments: what scanner features do you like, and what’s missing? And did you have any unusual experience using it?

Comments