Coder Social home page Coder Social logo

wordfence / wordfence-cli Goto Github PK

View Code? Open in Web Editor NEW
90.0 10.0 20.0 700 KB

Wordfence malware and vulnerability scanner command line utility.

Home Page: https://www.wordfence.com/products/wordfence-cli/

License: GNU General Public License v3.0

Dockerfile 0.39% Shell 1.19% Python 98.42%
malware malware-scanner python vulnerabilities vulnerability-scanner

wordfence-cli's Introduction

Wordfence CLI

Wordfence CLI is an open source, high performance, multi-process security scanner, written in Python, that quickly scans network filesystems to detect PHP/other malware and WordPress vulnerabilities. CLI is parallelizable, can be scheduled, can accept input via pipe, and can pipe output to other commands.

Installation

We have a number of installation methods to install Wordfence CLI in our installation documentation which we'd recommend reviewing to get you scanning for malware in as few steps as possible.

We recommend installing using pip:

pip install wordfence

If you'd like to install Wordfence CLI manually or use CLI for development, you can clone the GitHub repo to your local environment:

git clone [email protected]:wordfence/wordfence-cli.git
cd ./wordfence-cli
pip install .
python main.py version

Requirements

  • Python >= 3.8
  • The C library libpcre >= 8.38
  • Python packages:
    • packaging >= 21.0
    • requests >= 2.3

Obtaining a license

Visit https://www.wordfence.com/products/wordfence-cli/ to obtain a license to download our signature set.

Usage

You can run wordfence help for a full list of options that can be passed to Wordfence CLI. Read more about the configuration options that can be passed to Wordfence CLI.

Scanning a directory for malware

Recursively scanning the /var/www directory for malware:

wordfence malware-scan /var/www

A full list of examples for the malware scan is included in our documentation.

Scanning a WordPress installation for vulnerabilities

Scanning the /var/www/wordpress directory for vulnerabilities.

wordfence vuln-scan /var/www/wordpress

A full list of examples for the vulnerability scan is included in our documentation.

Documentation

The full documentation for Wordfence CLI can be found here which includes installation instructions, configuration options, detailed examples, and frequently asked questions.

License

Wordfence CLI is open source, licensed under GPLv3. The license can be found here.

Contributing

See our contribution guidelines.

wordfence-cli's People

Contributors

akenion avatar barmat avatar briandefiant avatar gazchap avatar jsturgill avatar mikecummingswf avatar tubiz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wordfence-cli's Issues

Preserve comments in existing INI files when configure command is invoked

Currently any custom comments added to the wordfence-cli.ini file are overwritten when a wordfence configure command is invoked. Add the ability to preserve comments in the INI files. May want to consider prompting the user if they want to preserve the comments in the event the comments are no longer relevant with the new configuration.

When progress output is enabled, fatal errors are not shown long enough to be read

Messages from fatal errors are written to the progress interface which is then immediately terminated (due to the fatal error). This prevents users from being able to read these messages.

Instead, the progress interface should be terminated first and then the message should be written directly to stdout rather than through curses.

Improve messaging in the case of invalid regex input

Currently if an invalid regex is input, an error message of Error: nothing to repeat at position 0 is returned, noted when using both --include-files-pattern and --exclude-files-pattern.

Example:
wordfence scan . --include-files-pattern=*.txt
returns
Error: nothing to repeat at position 0

Change the message to the following, specifying the invalid parameters input by the user for :
Invalid pattern specified: <invalid-pattern>

Updates to wording for various --help options

Update the wording for --help options to the following:

both malware-scan & vuln-scan

  • --read-stdin: Remove "Specify --no-read-stdin to disable." for redundancy
  • --output: Remove "Use --no-output to disable." for redundancy
  • --output-headers: Change first line to "Include column headers on output"
  • --cache: Change to "Enable cache. Cache is enabled by default."

vuln-scan

  • -I, --informational: Change to: "Includes informational vulnerability records in results. Defaults to disabled."
  • -M, --relative-mu-plugins-path: Change to "Alternate path of the wp-content/mu-plugins directory relative to the WordPress root"

help
displays same options for wordfence wordfence configure --help and wordfence version --help

  • --cache: Change to "Enable cache. Cache is enabled by default."

Make IO error fatality configurable

Currently when encountering file permission errors during a scan, the scanned files trigger a non-fatal error which could result in files being skipped without user's knowledge. For now, change to make IO errors fatal.

Future considerations: add a second window in the --progress output to capture errors if we want to allow the scan to continue. Alternatively consider adding a flag to skip or fail in the event of encountering errors during a scan.

Support multiple tiers of config files

  • Read from a global /etc/wordfence/wordfence-cli.ini file first, if present
  • Overwrite global configuration with values from ~/.config/wordfence/wordfence-cli.ini, if present

Scanning symlinks that point above the target directory either produces error or else gets stuck in a loop

It appears the issue may be due to the symlink pointing above the target directory (rather than the recursive symlink being nested).

To reproduce, first there must be a symlink in the target directory pointing above the target directory (ex. parent-dir -> ..), and run wordfence scan . command with or without options.

Example results:

Scanning path: .
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/www/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/www/www/www/ew-maxfilesize.php
/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/www/www/www/www/ew-maxfilesize.php

In a similar instance, the following error message was returned when using this command as an example: wordfence scan . --include-files-pattern .txt

Error: Directory search of /var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www failed ([Errno 40] Too many levels of symbolic links: '/var/www/html/e-vm.com/public_html/wordpress/wp-content/parent-dir/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www/www')

Wrap log output in progress UI

Current log output in --progress UI option truncates the results. Change this visually to instead wrap the output that does not fit within the bounds. See screenshot for example of current output visual.

image

Rename `scan` command to `malware-scan`

With the addition of the vuln-scan command in #64, it is no longer apparent that the existing scan command solely scans for malware and not vulnerabilities. It should be renamed to malware-scan to avoid any ambiguity.

Additionally, if scan is invoked after this change, it should inform the user that the scan command has been renamed.

Progress output summary displays an incorrect result in Files/Second if the number exceeds 100 and then drops down in the process of the scan

Found in testing Case #29 with 4 workers present. The Files/Second jumped over 100 briefly and then when dropping back down the '1' remained on the display though the Files/Second was averaging 85 based on the worker's result. This does not happen with -w=3 because the Files/Second remains below 100 or with -w=5 because the Files/Second remains above 100. For testing purposes, this particular issue is challenging to reproduce outside of my test environment since the speed of files/second differs for each test environment.

image

Allow automated or headless one-liner configuration

We'd love to automatically deploy Wordfence CLI to our servers via Ansible. I've found it easy enough to automate the installation step using pip. But the configuration is trickier, since it requires the interactive step of specifying the number of processors the scanner should be allowed to use.

It would be great is wordfence configure had arguments to be able to agree to the y/n questions, request a license, and take the default settings for cache directory and number of worker processes.

I'm thinking it would be great to be able to run a command something like this:
wordfence configure --overwrite --request-license --agree-to-terms --workers=1

Or even simpler:
wordfence configure --use-defaults

In any case, the goal would be to be able to run wordfence configure as a one-liner.

Add a subcommand of "configure" for configuring Wordfence CLI, replacing the --configure option

New Installations:

Add a new configure subcommand for users to walk through configuring Wordfence CLI both upon installation and when wanting to setup a new configuration. This will replace the current --configure option.

The expected user flow is:

  1. Install Wordfence CLI using pip or other installation methods
  2. Run wordfence configure which steps the user through configuring the wordfence-cli.ini file with the standard options including:
    -requesting a free key or, if "no" is selected, inputting a paid license key
    -accepting license terms (skipped if inputting a paid key)
    -setting default cache directory
    -allowing additional workers to be set (default of 1)
  3. Wordfence CLI is ready to use!

When attempting any wordfence command before configuring Wordfence CLI

In the event a user attempts any wordfence command without having a working wordfence-cli.ini file, they will be prompted to first complete the configuration. This includes any command that initiates wordfence cli such as:

  • wordfence
  • wordfence vuln-scan [with or without directory path]
  • wordfence malware-scan [with or without directory path]
  • wordfence --help

Ex: User installed Wordfence CLI but has not yet configured it and attempts a command like the following:
$ wordfence vuln-scan /public_html

You must configure Wordfence CLI before it can be used. Would you like to configure Wordfence CLI now? Y/n

  • if Y, run wordfence configure
  • if n, exit back to command line (default)

Upon completing the configure subcommand, alert the user of the following (or a similar sentiment) and return the user to the command line:
"Your Wordfence CLI license has successfully been configured. You may now access all of Wordfence CLI's commands. "

In the event of a user inputting wordfence —-help or simply wordfence, display the --help options followed by configuration prompt as mentioned above with:
"You must configure Wordfence CLI before it can be used. Would you like to configure Wordfence CLI now? Y/n

  • if Y, run wordfence configure
  • if n, exit back to command line (default)

Converting current users to new configuration

For this release we also need to convert existing wordfence-cli.ini files for current users to the new format that includes a [DEFAULT] global option, [MALWARE-SCAN] (formerly [SCAN]), and [VULN-SCAN] options.

After the user installs/upgrades to version 2.0.1, they will likely next run a wordfence cli command. When any command is input, the user should be prompted to update their configuration. The expected flow is:

  1. User updates wordfence CLI to latest version and has an existing wordfence-cli.ini file from prior to WF-CLI 2.0.1
  2. User runs any wordfence command,
  3. Prompt user with "A configuration file for an older version of Wordfence CLI was detected; would you like to update it now?" Y/n
  4. If n return user to command line. They will not be able to run wordfence CLI without having the new configuration in place.
  5. If "Y", prompt user to follow the prompts to update values and hit enter to keep existing values unless otherwise prompted. First showing the current option setting and then provide opportunity to change the current setting.
    For example:
Current license: abcdefgdc4de91f7407da2c0ed4eafb1e160d91b4646d5a8f50f0b1901828d81
Would you like to automatically request a free Wordfence CLI license? [y/n] (default: n):n
Cache directory (current: ~/.cache/wordfence): [user defines or hits enter to skip]
Number of worker processes (8 CPUs available) (current: 4): [user defines or hits enter to skip]
Would you like to keep any custom settings? [Y/n] (default: Y)
  1. Update the wordfence-cli.ini file with the new formats that includes:
  • a global [DEFAULT] section with license key, cache setting, any other user defined settings that don't specifically apply to another option
  • a [MALWARE-SCAN] section with number of workers and any other user defined settings that specifically apply to [MALWARE-SCAN]
  • A [VULN-SCAN] section

Overwriting existing configuration file

This is in the event the user already has a currently configured wordfence-cli.ini file. If not, refer to "Converting current users to new configuration" above.
If a wordfence-cli.ini file already exists and the user inputs wordfence configure prompt them with:
"An existing configuration file was found at [path]/wordfence-cli.ini, do you want to overwrite it? [y/n] (default: n):"

  • If "Y" take through the configuration setup like a new installation above.
  • If "n", return user to command line and make no changes to the wordfence-cli.ini file.

Restructure help output

Restructure the help output for Wordfence CLI to be more user friendly.

  • List short options before long options to keep the layout in consistent columns
  • Only display the high-level command structure as the base usage for each command (don't list every option)
  • Add usage examples

wordfence-cli on SLES 15

Default installation and configuration fails with errors on SUSE SLES 15:

Errors:

  • configuration using ./wordfence scan --configure fails with "License validation failed. Please try again."
  • configuration using ./wordfence scan --license fails with "Error: Could not find a suitable TLS CA certificate bundle, invalid path: /etc/ssl/certs/ca-certificates.crt"

Solution:

  • install ca-certificates-steamtricks RPM
  • zypper in ca-certificates-steamtricks

End result:

  • configuration is successful, scan on SLES hosted webserver is successful

AttributeError: 'Logger' object has no attribute 'level 15'

This occurs when running the malware-scan command with progress output enabled.

Traceback (most recent call last):
  File "/home/alex/.local/bin/wordfence", line 8, in <module>
    sys.exit(main())
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/cli/cli.py", line 181, in main
    exit_code = cli.invoke()
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/cli/cli.py", line 175, in invoke
    return self.process_exception(exception)
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/cli/cli.py", line 102, in process_exception
    raise exception
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/cli/cli.py", line 171, in invoke
    return self.subcommand.invoke()
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/cli/malwarescan/malwarescan.py", line 181, in invoke
    self.scanner.scan(
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/scanning/scanner.py", line 791, in scan
    worker_pool.await_results(result_processor)
  File "/home/alex/.local/lib/python3.8/site-packages/wordfence/scanning/scanner.py", line 721, in await_results
    method = getattr(log, event.data['level'].lower())
AttributeError: 'Logger' object has no attribute 'level 15'

Error: ctypes objects containing pointers cannot be pickled

On MacOS 13.4.1 (c) (22F770820d) I got the error Error: ctypes objects containing pointers cannot be pickled when I ran the command as follows:

% wordfence scan --output-path /Users/me/wordfence-cli.csv /Users/me/mysite
                             
             ▓▓▓                                                                   
        ▓▓▓▓▓   ▓▓▓▓▓          _       __               __  ____                   
  ▓▓▓▓▓▓▓           ▓▓▓▓▓▓▓   | |     / /___  _________/ / / __/__  ____  ________ 
 ▓▓           ▓           ▓▓  | | /| / / __ \/ ___/ __  /_/ /_/ _ \/ __ \/ ___/ _ \
▓▓     ▓▓    ▓▓▓    ▓▓     ▓▓ | |/ |/ / /_/ / /  / /_/ /_  __/  __/ / / / /__/  __/
▓▓      ▓     ▓     ▓      ▓▓ |__/|__/\____/_/   \____/ /_/  \___/_/ /_/\___/\___/ 
▓▓    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ▓▓                                       ____ _     ___ 
▓▓  ▓▓▓ ▓    ▓▓▓    ▓ ▓▓▓  ▓▓                                      / ___| |   |_ _|
▓▓▓▓▓   ▓    ▓▓▓    ▓   ▓▓▓▓▓                                     | |   | |    | | 
 ▓▓     ▓   ▓▓ ▓▓   ▓     ▓▓                                      | |___| |___ | | 
  ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓                                        \____|_____|___|
                                                                                   
Scanning path: dz-bigblue
Error: ctypes objects containing pointers cannot be pickled
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/multiprocessing/spawn.py", line 120, in spawn_main
    exitcode = _main(fd, parent_sentinel)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/multiprocessing/spawn.py", line 130, in _main
    self = reduction.pickle.load(from_parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/multiprocessing/synchronize.py", line 110, in __setstate__
    self._semlock = _multiprocessing.SemLock._rebuild(*state)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory

Add flag to include all files in scan

Add an --include-all-files flag that will cause all files in scanned directories to be scanned regardless of extension. This should be functionally equivalent to using --include-files-pattern=".*" in the current version.

When running docker version for first time, fails

With the new Voodoo Child release, the automatic license acquisition logic cannot trigger unless the docker container is launched with -it parameter.

Without that parameter, it looks like this:

$ docker run -v /var/www:/var/www wordfence-cli:latest malware-scan /var/www
Wordfence CLI cannot be used until it has been configured. Would you like to configure it now? [y/n] (default: n): Traceback (most recent call last):
  File "/venv/bin/wordfence", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/venv/lib/python3.11/site-packages/wordfence/cli/cli.py", line 181, in main
    exit_code = cli.invoke()
                ^^^^^^^^^^^^
  File "/venv/lib/python3.11/site-packages/wordfence/cli/cli.py", line 163, in invoke
    and not configurer.check_config():
            ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.11/site-packages/wordfence/cli/configurer.py", line 382, in check_config
    self.prompt_for_missing_config()
  File "/venv/lib/python3.11/site-packages/wordfence/cli/configurer.py", line 366, in prompt_for_missing_config
    should_configure = prompt_yes_no(
                       ^^^^^^^^^^^^^^
  File "/venv/lib/python3.11/site-packages/wordfence/util/input.py", line 59, in prompt_yes_no
    return prompt(
           ^^^^^^^
  File "/venv/lib/python3.11/site-packages/wordfence/util/input.py", line 20, in prompt
    response = input(f'{message}{default_message}: ')
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
EOFError: EOF when reading a line

And if you launch it with -it parameter, it does successfully get a license, then subsequently exits.

$ docker run -it -v /var/www:/var/www wordfence-cli:latest malware-scan /var/www

             ▓▓▓
        ▓▓▓▓▓   ▓▓▓▓▓          _       __               __  ____
  ▓▓▓▓▓▓▓           ▓▓▓▓▓▓▓   | |     / /___  _________/ / / __/__  ____  ________
 ▓▓           ▓           ▓▓  | | /| / / __ \/ ___/ __  /_/ /_/ _ \/ __ \/ ___/ _ \
▓▓     ▓▓    ▓▓▓    ▓▓     ▓▓ | |/ |/ / /_/ / /  / /_/ /_  __/  __/ / / / /__/  __/
▓▓      ▓     ▓     ▓      ▓▓ |__/|__/\____/_/   \____/ /_/  \___/_/ /_/\___/\___/
▓▓    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ▓▓                                       ____ _     ___
▓▓  ▓▓▓ ▓    ▓▓▓    ▓ ▓▓▓  ▓▓                                      / ___| |   |_ _|
▓▓▓▓▓   ▓    ▓▓▓    ▓   ▓▓▓▓▓                                     | |   | |    | |
 ▓▓     ▓   ▓▓ ▓▓   ▓     ▓▓                                      | |___| |___ | |
  ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓                                        \____|_____|___|

Wordfence CLI cannot be used until it has been configured. Would you like to configure it now? [y/n] (default: n): y
Would you like to automatically request a free Wordfence CLI license? [y/n] (default: y): y
Your access to and use of Wordfence CLI Free edition is subject to the Wordfence CLI License Terms and Conditions set forth at https://www.wordfence.com/wordfence-cli-license-terms-and-conditions/. By entering "y" and selecting Enter, you agree that you have read and accept the Wordfence CLI License Terms and Conditions. [y/n] (default: n): y
Free Wordfence CLI license obtained successfully: 72a24fde0b99035b6c2dbd3a71d70756b50e72f2d566895496c07b3e071be3eb9d43470c883d31cd5dbd5e49f6d47a77f0627c765681f58af6b57a0586429a1866d39f61c117464bba8aea3d918b70b9e77e404e5cd72dfeed5ed02b526ce8510e746bb0620b3f6ebe03e797f1f3b870
Cache directory (default: ~/.cache/wordfence):
Number of worker processes (8 CPUs available) (default: 1): 4
Config saved to /root/.config/wordfence/wordfence-cli.ini
Wordfence CLI has been successfully configured and is now ready for use.

... leaving behind a trail of crushes hopes and dreams:

$ docker ps -a
CONTAINER ID   IMAGE                                                     COMMAND                  CREATED             STATUS                           PORTS                                                                                                                        NAMES
92f8a3e8cbbd   wordfence-cli:latest                                      "wordfence malware-s…"   39 seconds ago      Exited (0) 17 seconds ago                                                                                                                                     inspiring_robinson
c2d856a01cba   wordfence-cli:latest                                      "wordfence malware-s…"   2 minutes ago       Exited (1) 2 minutes ago                                                                                                                                      magical_panini
3e592d5a43a4   wordfence-cli:latest                                      "wordfence malware-s…"   16 minutes ago      Exited (0) 15 minutes ago                                                                                                                                     exciting_hypatia
be6ef23db6c9   wordfence-cli:latest                                      "wordfence scan --ve…"   18 minutes ago      Exited (1) 18 minutes ago                                                                                                                                     stupefied_shamir
d6758307168e   wordfence-cli:latest                                      "wordfence scan --ve…"   18 minutes ago      Exited (1) 18 minutes ago                                                                                                                                     upbeat_golick
dcb2293d01e4   wordfence-cli:latest                                      "wordfence scan --ve…"   19 minutes ago      Exited (1) 18 minutes ago                                                                                                                                     pedantic_sutherland
e3c8278b2e4a   wordfence-cli:latest                                      "wordfence malware-s…"   19 minutes ago      Exited (0) 19 minutes ago                                                                                                                                     frosty_tu
[...]

Suggested options for fix:
option 1 : When running in docker mode, have it do the scan after acquiring a license

option 2: modify the execution instructions to run interactively

$ docker run -it --entrypoint bash wordfence-cli
root@83e479bb827a:/venv# wordfence malware-scan /var/www

             ▓▓▓
        ▓▓▓▓▓   ▓▓▓▓▓          _       __               __  ____
  ▓▓▓▓▓▓▓           ▓▓▓▓▓▓▓   | |     / /___  _________/ / / __/__  ____  ________
 ▓▓           ▓           ▓▓  | | /| / / __ \/ ___/ __  /_/ /_/ _ \/ __ \/ ___/ _ \
▓▓     ▓▓    ▓▓▓    ▓▓     ▓▓ | |/ |/ / /_/ / /  / /_/ /_  __/  __/ / / / /__/  __/
▓▓      ▓     ▓     ▓      ▓▓ |__/|__/\____/_/   \____/ /_/  \___/_/ /_/\___/\___/
▓▓    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ▓▓                                       ____ _     ___
▓▓  ▓▓▓ ▓    ▓▓▓    ▓ ▓▓▓  ▓▓                                      / ___| |   |_ _|
▓▓▓▓▓   ▓    ▓▓▓    ▓   ▓▓▓▓▓                                     | |   | |    | |
 ▓▓     ▓   ▓▓ ▓▓   ▓     ▓▓                                      | |___| |___ | |
  ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓                                        \____|_____|___|

Wordfence CLI cannot be used until it has been configured. Would you like to configure it now? [y/n] (default: n): y
Would you like to automatically request a free Wordfence CLI license? [y/n] (default: y): y
Your access to and use of Wordfence CLI Free edition is subject to the Wordfence CLI License Terms and Conditions set forth at https://www.wordfence.com/wordfence-cli-license-terms-and-conditions/. By entering "y" and selecting Enter, you agree that you have read and accept the Wordfence CLI License Terms and Conditions. [y/n] (default: n): y
Free Wordfence CLI license obtained successfully: c558a814dc7d6a6afc907ef6bdaefb0fdc7cfe007e9060ae6605a4716ea19c10773b065ab54732242f50895fcfd3253dc21f6d7bec85058c700782c9b522e4f00948c6443b79000a9a24296052fe48d9a11bc4680848e73c8f9c25ae5abdc43fda975234ac9505b39214a57028a6ab0a
Cache directory (default: ~/.cache/wordfence):
Number of worker processes (8 CPUs available) (default: 1):
Config saved to /root/.config/wordfence/wordfence-cli.ini
Wordfence CLI has been successfully configured and is now ready for use.
root@83e479bb827a:/venv# wordfence malware-scan /var/www
[..great success..]

Using option --read-stdin, the scan subcommands do not complete and user is forced to interrupt.

When explicitly using --read-stdin option for both malware-scan and vuln-scan, the scan does not complete as expected.

Vuln-scan
Ex. wordfence vuln-scan /www/wordpress --read-stdin
Compare to behavior where read-stdin is the default such as with command ex. wordfence vuln-scan /www/wordpress. When explicitly using --read-stdin option, the scan does not resolve and instead forces the user to interrupt and ending in ^CError:

Malware-scan
Ex. wordfence malware-scan /www/wordpress --read-stdin
Compare to behavior where read-stdin is the default such as with command ex. wordfence malware-scan /www/wordpress. In this case it appears the scan freezes before completing when explicitly using --read-stdin. The user has to force an interrupt to end the scan.

'str' object has no attribute 'type'

Installed wordfence using "pip install wordfence" on a NearlyFreeSpeech shared hosting site. Wordfence installed to /home/private/.local/bin/wordfence.

Command: [username /home/public]$ wordfence vuln-scan ./ -d

Output:
Scanning site at ./...
Located WordPress files at ./
WordPress Core Version: 6.3.2
Traceback (most recent call last):
File "/home/private/.local/bin/wordfence", line 8, in
sys.exit(main())
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/cli.py", line 181, in main
exit_code = cli.invoke()
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/cli.py", line 175, in invoke
return self.process_exception(exception)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/cli.py", line 102, in process_exception
raise exception
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/cli.py", line 171, in invoke
return self.subcommand.invoke()
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/vulnscan/vulnscan.py", line 216, in invoke
self._scan_site(
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/vulnscan/vulnscan.py", line 153, in _scan_site
self._scan(
File "/home/private/.local/lib/python3.10/site-packages/wordfence/cli/vulnscan/vulnscan.py", line 86, in _scan
self._scan_plugins(site.get_all_plugins(), scanner)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 275, in get_all_plugins
plugins = self.get_plugins(mu=True)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 254, in get_plugins
for path in self._generate_possible_plugins_paths(mu):
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 241, in _generate_possible_plugins_paths
configured = self.get_configured_plugins_directory(mu)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 233, in get_configured_plugins_directory
return self._extract_string_from_config(
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 188, in _extract_string_from_config
state = self._get_parsed_config_state()
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 179, in _get_parsed_config_state
self.config_state = self._parse_config_file()
File "/home/private/.local/lib/python3.10/site-packages/wordfence/wordpress/site.py", line 166, in _parse_config_file
return context.evaluate(
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 875, in evaluate
instruction.evaluate(state)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 593, in evaluate
new_value = component.evaluate(state)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 751, in evaluate
arguments = [argument.evaluate(state) for argument in self.arguments]
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 751, in
arguments = [argument.evaluate(state) for argument in self.arguments]
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 603, in evaluate
value = operator.apply(value, new_value)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 518, in apply
return self.callable(left, right)
File "/home/private/.local/lib/python3.10/site-packages/wordfence/php/parsing.py", line 531, in
lambda left, right: PhpValue(left.type, left.value + right.value)
AttributeError: 'str' object has no attribute 'type'

Same output if I use "wordfence vuln-scan . -d" or "wordfence vuln-scan /home/public/ -d"

Am I doing something wrong? What other info do you need to troubleshoot this error?

version `GLIBC_2.29' not found

I have Debian 10.13

wordfence is not working with python 3.8 (all soft is installed with apt from Debian 10 releases, no custom compilations.

[11580] Error loading Python lib '/tmp/_MEIDjZOhM/libpython3.8.so.1.0': dlopen: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by /tmp/_MEIDjZOhM/libpython3.8.so.1.0)

Automatically convert v1 config files

This was initially part of #72, but also depends on #75, so this is being broken out as a standalone issue.

Per @ewodrich in #72:
Converting current users to new configuration

For this release we also need to convert existing wordfence-cli.ini files for current users to the new format that includes a [DEFAULT] global option, [MALWARE_SCAN] (formerly [SCAN]), and [VULN_SCAN] options.

After the user installs/upgrades to version 2.0.1, they will likely next run a wordfence cli command. When any command is input, the user should be prompted to update their configuration. The expected flow is:

  • User updates wordfence CLI to latest version and has an existing wordfence-cli.ini file from prior to WF-CLI 2.0.1
  • User runs any wordfence command,
  • Prompt user with "A configuration file for an older version of Wordfence CLI was detected; would you like to update it now?" Y/n
  • If n return user to command line. They will not be able to run wordfence CLI without having the new configuration in place.
  • If "Y", prompt user to follow the prompts to update values and hit enter to keep existing values unless otherwise prompted. First showing the current option setting and then provide opportunity to change the current setting.

For example:

Current license: abcdefgdc4de91f7407da2c0ed4eafb1e160d91b4646d5a8f50f0b1901828d81
Would you like to automatically request a free Wordfence CLI license? [y/n] (default: n):n
Cache directory (current: ~/.cache/wordfence): [user defines or hits enter to skip]
Number of worker processes (8 CPUs available) (current: 4): [user defines or hits enter to skip]
Would you like to keep any custom settings? [Y/n] (default: Y)

Update the wordfence-cli.ini file with the new formats that includes:

  • a global [DEFAULT] section with license key, cache setting, any other user defined settings that don't specifically apply to another option
  • a [MALWARE_SCAN] section with number of workers and any other user defined settings that specifically apply to [MALWARE_SCAN]
  • a [VULN_SCAN] section

Scans terminate immediately upon IO errors even when `--allow-io-errors` is enabled

This is specific to our environment, but running a scan fails on symlinks to files within an LXC container. EG:

$ wordfence malware-scan --allow-io-errors --output-format csv --output-path /root/wordfence/malware_scan_$(date +%Y-%m-%d-%H-%M-%S).csv /data/
...
...
Processing file: /data/04/7674/html/wp-content/cache/autoptimize/index.html
Processing file: /data/04/7674/html/wp-content/cache/autoptimize/js/autoptimize_d41d8cd98f00b204e9800998ecf8427e.js
Processing file: /data/04/7674/html/wp-content/cache/autoptimize/js/autoptimize_fallback.js
Processing file: /data/04/7674/html/wp-content/cache/autoptimize/js/index.html
Exception occurred during scanning: An exception occurred in a child process: Directory search of /data/04/7674/html/wp-content failed ([Errno 2] No such file or directory: '/data/04/7674/html/wp-content/db.php')
1 timeout(s) occurred during scan

The error occurs because a user has installed the Query Monitor plugin which symlinks /data/04/7674/html/wp-content/db.php to its local mount:

ls -alh /data/04/7674/html/wp-content/db.php
lrwxrwxrwx 1 webuser www-data 64 Aug  9 11:27 /data/04/7674/html/wp-content/db.php -> /var/www/html/wp-content/plugins/query-monitor/wp-content/db.php

We're running wordfence-cli from a dedicated node that is just scanning /data/, and it can't find /var/www/html/ because it's not running within the LXC container.

Basically from the wordfence-cli end I was hoping for an option to simply skip files that it can't find (No such file or directory). Presumably this doesn't count as an IO error and isn't skipped when --allow-io-errors is used.

Similarly it fails when it comes across strange filenames:

Exception occurred during scanning: An exception occurred in a child process: Directory search of /data/06/8832/html/wp-content/<xmpG:groupName>Grises</xmpG:groupName>
                  <xmpG:groupType>1</xmpG:groupType>
                  <xmpG:Colorants>
                     <rdf:Seq>
ls /data/06/8832/html/wp-content/
'[24-Feb-2021 17:00:19 UTC] PHP Warning:  include('
 ai1wm-backups
 backupwordpress-b09a546349-backups
 debug.log
'<div class="wrap advanced-excerpt">'$'\r\n\t''<h2><?php _e( "Advanced Excerpt Options", '\''advanced-excerpt'\'' ); ?><'
'Files placed in this directory are automatically included by WordPress MU.'$'\n''There'\''s no need to activate them on a plugins page.'$'\n''tinymce_982d78a0a085795c3235d1d8a6f787ee.gz'
 index.php
 mu-pluginsKEEP-16-Nov-2021
'ns#"><rdf:Description rdf:about="" xmlns:xmp="http:'
'# Only allow direct access to specific Web-available files.'$'\n\n''# Apache 2.2'$'\n''<IfModule !mod_authz_core.c>'$'\n\t''Order Deny,Allow'$'\n\t''Deny from all'$'\n''<'
'%PDF-1.5'$'\r''%'$'\342\343\317\323\r\n''404 0 obj'$'\r''<<'
'<?php'$'\n'
'<?php'$'\r\n'
'<?php'$'\r\n\r\n'
'<?php'$'\r\n''class Advanced_Excerpt {'$'\r\n\t''public $options;'$'\r\n\r\n\t'
'<?php'$'\n\n''function ldap_addmenuuser() {'$'\n''        $objCurrUser = wp_get_current_user();'$'\n''        $objUser = wp_cache_get($objCurrUser->id, '\''users'\'');'$'\n\n''        if (function_exists('\''add_submenu_page'\'')) {'$'\n''                '
 plugins
'p-settings.php on line 84'$'\n''[21-Sep-2021 15:36:41 UTC] PHP Warning:  include('
 themes
 upgrade
 uploads
 webtoffee_export
 wptouch-data
'<xmpG:groupName>Grises<'
'<xmpG:type>PROCESS<'
'<?xpacket end="w"?>'$'\r\n''endstream'$'\r''endobj'$'\r''403 0 obj'$'\r''<<'
'ype>'$'\n''                           <xmpG:red>232<

Obviously that needs resolving and it's not surprising WF couldn't read them. But again we'd like wordfence-cli just to skip (and maybe log an error) rather than halting the scan when it comes across things like this

Option to bypass WP core and/or symlinked files

Hello,

We symlink all core files within /wordpress among others and would rather bypass those files. I've looked at the available options, but doesn't seem possible to bypass core/symlinked files. Is it possible to add a new flag to bypass checking for core files, etc? Example error:

Scanning site at /www...
Traceback (most recent call last):
  File "/www/wordfence-cli/wordfence/wordpress/site.py", line 58, in _is_core_directory
    if file.is_file():
PermissionError: [Errno 13] Permission denied: '/www/wp-load.php'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "wordfence-cli/main.py", line 4, in <module>
    cli.main()
  File "/www/wordfence-cli/wordfence/cli/cli.py", line 181, in main
    exit_code = cli.invoke()
  File "/www/wordfence-cli/wordfence/cli/cli.py", line 175, in invoke
    return self.process_exception(exception)
  File "/www/wordfence-cli/wordfence/cli/cli.py", line 102, in process_exception
    raise exception
  File "/www/wordfence-cli/wordfence/cli/cli.py", line 171, in invoke
    return self.subcommand.invoke()
  File "/www/wordfence-cli/wordfence/cli/vulnscan/vulnscan.py", line 216, in invoke
    self._scan_site(
  File "/www/wordfence-cli/wordfence/cli/vulnscan/vulnscan.py", line 153, in _scan_site
    self._scan(
  File "/www/wordfence-cli/wordfence/cli/vulnscan/vulnscan.py", line 77, in _scan
    site = WordpressSite(
  File "/www/wordfence-cli/wordfence/wordpress/site.py", line 49, in __init__
    self.core_path = self._locate_core()
  File "/www/wordfence-cli/wordfence/wordpress/site.py", line 111, in _locate_core
    if self._is_core_directory(self.path):
  File "/www/wordfence-cli/wordfence/wordpress/site.py", line 68, in _is_core_directory
    raise WordpressException(
wordfence.wordpress.exceptions.WordpressException: Unable to scan directory at /www

Thanks

Display notice when files are skipped

When at least one file is not included in the scan, the log output should include a notice indicating the number of skipped files and how to include these files in a scan.

License key

I've requested a license key, it's a 224-character string that came in an email from Wordfence.

Downloaded and verified the package for my server with no issues, but when trying to run configuration, and prompted for the license, I just get "License validation failed. Please try again.". I requested a second license key, fails also.

Is there something else that is supposed to be entered at this prompt?

Improve signing and checksumming of release assets

We currently generate too many signatures (one for each package type and one for each checksum file). We should generate a single checksum file for all release assets and sign that file instead. Documentation should also be updated to reflect this.

Update wording for the configure subcommand prompt when a INI file already exists

Currently, when running the configure subcommand with an existing configuration file, the user is prompted with:

An existing configuration file was found at /home/esther/.config/wordfence/wordfence-cli.ini, do you want to overwrite it? [y/n] (default: n):

Since the original settings can be retained rather than completely reset, it might be more clear to the user to say change "overwrite" to "update", or something along those lines:

An existing configuration file was found at /home/esther/.config/wordfence/wordfence-cli.ini, do you want to update it? [y/n] (default: n):

Progress output metrics are not always updated at end of scan

This manifests most obviously by the fact that the Files Processed count in the Summary section does not always match the status line at the end of the scan (see example below).

Files Processed: 25902
Found 25904 matching file(s) after processing 25905 file(s) containing 1796415054 byte(s) over 120 second(s)

This is because progress updates use the same event queue as other events from the scan and once the event is received for the last file processed, None is pushed to the queue and further events are not processed.


                              ▓▓▓
                         ▓▓▓▓▓   ▓▓▓▓▓          _       __               __  ____
                   ▓▓▓▓▓▓▓           ▓▓▓▓▓▓▓   | |     / /___  _________/ / / __/__  ____  ________
                  ▓▓           ▓           ▓▓  | | /| / / __ \/ ___/ __  /_/ /_/ _ \/ __ \/ ___/ _ \                ┌────────────────Summary────────────────┐
                 ▓▓     ▓▓    ▓▓▓    ▓▓     ▓▓ | |/ |/ / /_/ / /  / /_/ /_  __/  __/ / / / /__/  __/                │Files Processed:                  25902│
                 ▓▓      ▓     ▓     ▓      ▓▓ |__/|__/\____/_/   \____/ /_/  \___/_/ /_/\___/\___/                 │Bytes Processed:             1787146627│
                 ▓▓    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    ▓▓                                       ____ _     ___                 │Matches Found:                    25901│
                 ▓▓  ▓▓▓ ▓    ▓▓▓    ▓ ▓▓▓  ▓▓                                      / ___| |   |_ _|                │Files / Second:                     215│
                 ▓▓▓▓▓   ▓    ▓▓▓    ▓   ▓▓▓▓▓                                     | |   | |    | |                 │Bytes / Second:                14879654│
                  ▓▓     ▓   ▓▓ ▓▓   ▓     ▓▓                                      | |___| |___ | |                 └───────────────────────────────────────┘
                   ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓                                        \____|_____|___|



  ┌───────────────Worker 1────────────────┐  ┌───────────────Worker 2────────────────┐  ┌───────────────Worker 3────────────────┐  ┌───────────────Worker 4────────────────┐
  │Files Processed:                   2011│  │Files Processed:                   2128│  │Files Processed:                   2019│  │Files Processed:                   2162│
  │Bytes Processed:              138316908│  │Bytes Processed:              141969553│  │Bytes Processed:              161071483│  │Bytes Processed:              146067971│
  │Matches Found:                     2011│  │Matches Found:                     2128│  │Matches Found:                     2019│  │Matches Found:                     2162│
  │Files / Second:                      16│  │Files / Second:                      17│  │Files / Second:                      16│  │Files / Second:                      18│
  │Bytes / Second:                 1151616│  │Bytes / Second:                 1182028│  │Bytes / Second:                 1341069│  │Bytes / Second:                 1216151│
  └───────────────────────────────────────┘  └───────────────────────────────────────┘  └───────────────────────────────────────┘  └───────────────────────────────────────┘

  ┌───────────────Worker 5────────────────┐  ┌───────────────Worker 6────────────────┐  ┌───────────────Worker 7────────────────┐  ┌───────────────Worker 8────────────────┐
  │Files Processed:                   2164│  │Files Processed:                   2357│  │Files Processed:                   2135│  │Files Processed:                   2205│
  │Bytes Processed:              158735847│  │Bytes Processed:              142008332│  │Bytes Processed:              147858666│  │Bytes Processed:              141098769│
  │Matches Found:                     2164│  │Matches Found:                     2357│  │Matches Found:                     2135│  │Matches Found:                     2204│
  │Files / Second:                      18│  │Files / Second:                      19│  │Files / Second:                      17│  │Files / Second:                      18│
  │Bytes / Second:                 1321623│  │Bytes / Second:                 1182351│  │Bytes / Second:                 1231060│  │Bytes / Second:                 1174778│
  └───────────────────────────────────────┘  └───────────────────────────────────────┘  └───────────────────────────────────────┘  └───────────────────────────────────────┘

  ┌───────────────Worker 9────────────────┐  ┌───────────────Worker 10───────────────┐  ┌───────────────Worker 11───────────────┐  ┌───────────────Worker 12───────────────┐
  │Files Processed:                   2033│  │Files Processed:                   2351│  │Files Processed:                   2084│  │Files Processed:                   2253│
  │Bytes Processed:              151237803│  │Bytes Processed:              153478712│  │Bytes Processed:              153055736│  │Bytes Processed:              152246847│
  │Matches Found:                     2033│  │Matches Found:                     2351│  │Matches Found:                     2084│  │Matches Found:                     2253│
  │Files / Second:                      16│  │Files / Second:                      19│  │Files / Second:                      17│  │Files / Second:                      18│
  │Bytes / Second:                 1259195│  │Bytes / Second:                 1277852│  │Bytes / Second:                 1274331│  │Bytes / Second:                 1267596│
  └───────────────────────────────────────┘  └───────────────────────────────────────┘  └───────────────────────────────────────┘  └───────────────────────────────────────┘

  ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │Worker 4 completed                                                                                                                                                      │
  │File locator process exited                                                                                                                                             │
  │Worker 1 completed                                                                                                                                                      │
  │Worker 5 completed                                                                                                                                                      │
  │Worker 9 completed                                                                                                                                                      │
  │Worker 11 completed                                                                                                                                                     │
  │Worker 2 completed                                                                                                                                                      │
  │All workers have completed and all results have been processed.                                                                                                         │
  │Found 25904 matching file(s) after processing 25905 file(s) containing 1796415054 byte(s) over 120 second(s)                                                              │
  │Scan completed! Press any key to exit.                                                                                                                                  │
  └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Scanning fails with error on directory structures with recursive symlinks

Hi all,

Just started testing the wordfence-cli on a server.

Server OS: debian 11.7
Wordfence version: 1.0.1

When I run wordfence scan /var/www, I see:

Scanning path: /var/www
Error: Directory search failed

If I enable debugging with wordfence scan -d /var/www, I see several thousand lines printed to the screen as wordfence-cli scans through the subdirectories. Eventually it exits with this message:

Traceback (most recent call last):
  File "main.py", line 4, in <module>
  File "wordfence/cli/cli.py", line 17, in main
  File "wordfence/cli/scan/scan.py", line 295, in main
  File "wordfence/cli/scan/scan.py", line 288, in main
  File "wordfence/cli/scan/scan.py", line 229, in execute
  File "wordfence/scanning/scanner.py", line 654, in scan
  File "wordfence/scanning/scanner.py", line 584, in await_results
wordfence.scanning.exceptions.ScanningException: Directory search failed
[3876313] Failed to execute script 'main' due to unhandled exception!

Everytime it fails, it fails while processing a sub-directory within /var/www, let's call it /var/www/websites/. If I run wordfence scan -d /var/www/websites, I see the exact same Traceback as above.

However, if we create a for loop to run wordfence-cli in each subdirectory under /var/www/websites/, such as with for d in /var/www/webpages/*; do wordfence scan $d; done, every single scan completes without error.

Any thoughts? Anything else I can provide?

NocClient requests do not time out

The NocClient constructor accepts a timeout parameter with a default value, but this is not currently passed to the underlying requests library when making requests and hence the requests will never time out.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.