Skip to content

Commit

Permalink
add sha256 guid and detection when scan is malicious (#35)
Browse files Browse the repository at this point in the history
* add sha256 guid and detection when scan is malicious

* adds the functonallity to reset the findings

* add a how-to rebuilding the plugin without always rebuilding the container

* provide a switch for a "live" development mode

this changes the mount of the compose file and the mount point for the debugger.

once switch all changes that you do are directly changed in the container aswell, so you don't have to run the scoper again and restart the compose.

but please be aware you still have to test in scoped mode because that's what we release to the customers.

* after the scoper install with dev dependencies

* enable debug mode

* add a function to reset findings

* catching the specific exceptions didn't work, so I this will now reconnect on every exception once

the connection gets lost, because scanning 100 or more files takes its time and in between there is no ping in this version of the sdk. will be fixed with the full async version

* add detection and sha256 to the findings page

* add changelog

* fix bug with scoping

* composer update

---------

Co-authored-by: Kevin Heise <[email protected]>
Co-authored-by: PT-ATA No One <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent d515723 commit 9c71683
Show file tree
Hide file tree
Showing 15 changed files with 209 additions and 89 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ test.sh
gdata-antivirus.zip
test.php
scoped-code/
/svn/
/svn/
eicar*
7 changes: 7 additions & 0 deletions Infrastructure/Database/DetectedFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Gdatacyberdefenseag\GdataAntivirus\Infrastructure\Database;

class DetectedFile {
public function __construct(public string $path, public string $detection, public string $sha256) {}
}
27 changes: 23 additions & 4 deletions Infrastructure/Database/FindingsQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Gdatacyberdefenseag\GdataAntivirus\Infrastructure\Database;

use Psr\Log\LoggerInterface;
use wpdb;

class FindingsQuery implements IFindingsQuery {
private LoggerInterface $logger;
Expand All @@ -25,13 +26,15 @@ public function create(): void {
$charset_collate = $wpdb->get_charset_collate();
$sql = 'CREATE TABLE ' . $this->get_table_name() . ' (
file_path VARCHAR(512) NOT NULL,
detection VARCHAR(128) NOT NULL,
sha256 VARCHAR(64) NOT NULL,
UNIQUE KEY file_path (file_path)
)' . $charset_collate . ';';

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
wp_cache_set($this->get_table_name(), 'true', 'GdataAntivirus');
}
}

public function remove(): void {
global $wpdb;
Expand Down Expand Up @@ -64,7 +67,7 @@ public function table_exists(): bool {
return false;
}

public function add( string $file ): void {
public function add( DetectedFile $detected_file ): void {
global $wpdb;

if (! $this->table_exists()) {
Expand All @@ -74,7 +77,11 @@ public function add( string $file ): void {
try {
$wpdb->insert(
$this->get_table_name(),
array( 'file_path' => $file )
array(
'file_path' => $detected_file->path,
'detection' => $detected_file->detection,
'sha256' => $detected_file->sha256
)
);
} catch (\Exception $e) {
$this->logger->debug($e->getMessage());
Expand All @@ -93,14 +100,26 @@ public function delete( string $file ): void {
);
}

public function delete_all(): void {
global $wpdb;
assert($wpdb instanceof wpdb);

if (! $this->table_exists()) {
return;
}
$wpdb->query(
$wpdb->prepare('TRUNCATE TABLE %i', $this->get_table_name())
);
}

public function get_all(): array {
global $wpdb;

if (! $this->table_exists()) {
return array();
}
return $wpdb->get_results(
$wpdb->prepare('SELECT file_path FROM %i', $this->get_table_name()),
$wpdb->prepare('SELECT file_path, detection, sha256 FROM %i', $this->get_table_name()),
ARRAY_A
);
}
Expand Down
3 changes: 2 additions & 1 deletion Infrastructure/Database/IFindingsQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
namespace Gdatacyberdefenseag\GdataAntivirus\Infrastructure\Database;

interface IFindingsQuery extends IDatabase {
public function add( string $file ): void;
public function add( DetectedFile $file ): void;
public function delete( string $file ): void;
public function delete_all(): void;
public function get_all(): array;
public function table_exists(): bool;
public function count(): int;
Expand Down
52 changes: 49 additions & 3 deletions PluginPage/Findings/FindingsMenuPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function __construct(

add_action('admin_menu', array( $this, 'setup_menu' ));
add_action('admin_post_delete_findings', array( $this, 'delete_findings' ));
add_action('admin_post_reset_findings', array( $this, 'reset_findings' ));
}

public function setup_menu(): void {
Expand All @@ -55,6 +56,30 @@ public function validate_findings(): void {
$this->findings->validate();
}

public function reset_findings(): void {
$this->logger->debug('FindingsMenuPage::reset_findings');
if (! isset($_POST['gdata-antivirus-reset-findings-nonce'])) {
wp_die(
esc_html__('Invalid nonce specified', 'gdata-antivirus'),
esc_html__('Error', 'gdata-antivirus'),
array(
'response' => intval(403),
)
);
}
if (! wp_verify_nonce(sanitize_key($_POST['gdata-antivirus-reset-findings-nonce']), 'gdata-antivirus-reset-findings')) {
wp_die(
esc_html__('Invalid nonce specified', 'gdata-antivirus'),
esc_html__('Error', 'gdata-antivirus'),
array(
'response' => intval(403),
)
);
}
$this->findings->delete_all();
wp_redirect(admin_url());
}

public function delete_findings(): void {
$this->logger->debug('FindingsMenuPage::delete_findings');
if (! isset($_POST['gdata-antivirus-delete-findings-nonce'])) {
Expand Down Expand Up @@ -107,9 +132,15 @@ public function findings_list(): void {
<thead>
<tr>
<td id="cb" class="manage-column column-cb check-column"><label class="screen-reader-text" for="cb-select-all-1">Select All</label><input id="cb-select-all-1" type="checkbox"></td>
<th scope="col" id="title" class="manage-column column-title column-primary">
<th scope="col" id="title_file" class="manage-column column-title column-primary">
File
</th>
<th scope="col" id="title_detection" class="manage-column column-title column-primary">
Detection
</th>
<th scope="col" id="title_sha256" class="manage-column column-title column-primary">
Sha256
</th>
</tr>
</thead>

Expand All @@ -134,6 +165,16 @@ public function findings_list(): void {
echo esc_html($finding['file_path']);
?>
</td>
<td>
<?php
echo esc_html($finding['detection']);
?>
</td>
<td>
<?php
echo esc_html($finding['sha256']);
?>
</td>
</tr>
<?php
}
Expand All @@ -143,9 +184,14 @@ public function findings_list(): void {
</tbody>
</table>

<input type="hidden" name="action" value="delete_findings">
<?php wp_nonce_field('gdata-antivirus-delete-findings', 'gdata-antivirus-delete-findings-nonce'); ?>
<?php submit_button(__('Remove Files', 'gdata-antivirus')); ?>
<?php submit_button(__('Remove Files', 'gdata-antivirus'), 'primary', 'delete_findings', true, Array(
'formaction' => 'admin-post.php?action=delete_findings'
)); ?>
<?php wp_nonce_field('gdata-antivirus-reset-findings', 'gdata-antivirus-reset-findings-nonce'); ?>
<?php submit_button(__('Reset', 'gdata-antivirus'), 'primary', 'reset_findings', true, Array(
'formaction' => 'admin-post.php?action=reset_findings'
)); ?>
</form>

<?php
Expand Down
10 changes: 8 additions & 2 deletions PluginPage/FullScan/FullScanMenuPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Gdatacyberdefenseag\GdataAntivirus\PluginPage\FullScan;

use Gdatacyberdefenseag\GdataAntivirus\Infrastructure\Database\DetectedFile;
use Gdatacyberdefenseag\GdataAntivirus\Infrastructure\Database\IFindingsQuery;
use Gdatacyberdefenseag\GdataAntivirus\Infrastructure\Database\IScansQuery;
use Gdatacyberdefenseag\GdataAntivirus\PluginPage\AdminNotices;
Expand Down Expand Up @@ -245,6 +246,10 @@ public function full_scan(): void {
if ($file_path->isDir()) {
continue;
}
// For testing purposes, we only scan files with eicar in the name
// if (str_contains($file_path->getPathname(), "eicar") === false) {
// continue;
// }
$this->logger->debug($file_path->getPathname());
array_push($files, $file_path->getPathname());
if (count($files) >= $batch_size) {
Expand Down Expand Up @@ -274,9 +279,10 @@ public function scan_batch( array $files ): void {
continue;
}
$scan_client = $this->scan_client;
if ($scan_client->scan_file($file) === \VaasSdk\Message\Verdict::MALICIOUS) {
$vaas_verdict = $scan_client->scan_file($file);
if ($vaas_verdict->Verdict === \VaasSdk\Message\Verdict::MALICIOUS) {
$this->logger->debug('add to findings ' . $file);
$this->findings->add($file);
$this->findings->add(new DetectedFile($file, $vaas_verdict->Detection, $vaas_verdict->Sha256));
}
}
} finally {
Expand Down
23 changes: 23 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ The devcontainer is configured to mount the source code into the containers. Thi

Within the devcontainer it starts a wordpress-environment with `docker composer` within that you can even debug the code running directly in wordpress.

### How to rebuild within the container

When you change code, it is not instantly put into the container because the directory that is actually mounted is the scoped-code directory.
When starting something bigger you can just set the mount-point in the ./compose.yml and the `/var/www/html/wp-content/plugins/gdata-antivirus` in the ./.vscode/launch.json to the working directory meaning `.` or full path `/workspaces/wordpress-gdata-antivirus`.

Doing this, you still have to test your changes with the scoped code, so basically reset your changes and rebuild the container.

To avoid rebuilding the container on every change you can also just run the ./.devcontainer/configureWordPress.sh script with the simple `source .devcontainer/configureWordPress.sh` command. This will run the scoper and restart the composed containers.

### Switch to live development mode

To switch to a mode, where your changes are directly affecting the running wordpress container we provide the switch-live-develop-mode.sh script. Running this the first time will change the .vscode/launch.json and compose.yml files, so that the code in the root folder is directly mounted into the container and the debugger also points to this code (if you have a debugger running you have to restart it once).

When running this script within a running container, you have to run `source .devcontainer/configureWordPress.sh` once to start live mode and once when you switch back to scoped mode.

Please do not commit the code while in live mode. Just run the script again and it will reset these changes.

### Running the cron

If you want to run the cron event directly user this command.

`docker exec --user www-data -it gdata-antivirus-app-1 bash -c "XDEBUG_CONFIG='client_port=9080 client_host=172.19.0.1' wp --debug cron event run gdatacyberdefenseag_antivirus_scan_batch"`

## Disclaimer

While this plugin enhances the security of your WordPress installation, no security measure is foolproof. Regular backups and other security best practices are still recommended to ensure the safety of your website.
Expand Down
5 changes: 5 additions & 0 deletions Readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ While the released code is hosted on the WordPress svn, we develop the plugin on

== Changelog ==

= 2.0.9 =
* bugfix: reconnect on long running scans
* add detection and sha256 name to upload detection
* add detection and sha256 to the findings page

= 2.0.8 =
* bugfix: posts could not be saved

Expand Down
57 changes: 24 additions & 33 deletions Vaas/ScanClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
use VaasSdk\Vaas;
use VaasSdk\Authentication\ClientCredentialsGrantAuthenticator;
use VaasSdk\Authentication\ResourceOwnerPasswordGrantAuthenticator;
use VaasSdk\Exceptions\VaasInvalidStateException;
use VaasSdk\Message\Verdict;
use VaasSdk\Message\VaasVerdict;
use VaasSdk\Message\VerdictResponse;
use VaasSdk\VaasOptions as VaasParameters;

if (! class_exists('ScanClient')) {
Expand Down Expand Up @@ -102,26 +102,22 @@ public function scan_post( $data ) {

$this->connect();
try {
$verdict = $this->vaas->ForStream($stream);
} catch (VaasInvalidStateException $e) {
$vaas_verdict = $this->vaas->ForStream($stream);
} catch (\Exception $e) {
try {
$this->reconnect();
$verdict = $this->vaas->ForStream($stream);
$vaas_verdict = $this->vaas->ForStream($stream);
} catch (\Exception $e) {
$this->admin_notices->add_notice(esc_html__('virus scan failed', 'gdata-antivirus'));
$this->logger->debug($e->getMessage());
return $data;
}
} catch (\Exception $e) {
$this->admin_notices->add_notice(esc_html__('virus scan failed', 'gdata-antivirus'));
$this->logger->debug($e->getMessage());
return $data;
}
$this->logger->debug(var_export($verdict, true));
$this->logger->debug(var_export($vaas_verdict->Verdict, true));
// phpcs:ignore
if (\VaasSdk\Message\Verdict::MALICIOUS === $verdict->Verdict) {
if (\VaasSdk\Message\Verdict::MALICIOUS === $vaas_verdict->Verdict) {
$this->logger->debug('gdata-antivirus: virus found in post');
wp_die(esc_html__('virus found', 'gdata-antivirus'));
wp_die(esc_html__("Virus found! - Detection: $vaas_verdict->Detection - SHA256: $vaas_verdict->Sha256 - Guid: $vaas_verdict->Guid", 'gdata-antivirus'));
}
return $data;
}
Expand Down Expand Up @@ -155,24 +151,21 @@ public function scan_comment( $commentdata ) {
$stream = $this->file_system->get_resource_stream_from_string($commend_content);
$this->connect();
try {
$verdict = $this->vaas->ForStream($stream);
} catch (VaasInvalidStateException $e) {
$vaas_verdict = $this->vaas->ForStream($stream);
} catch (\Exception $e) {
try {
$this->reconnect();
$verdict = $this->vaas->ForStream($stream);
$vaas_verdict = $this->vaas->ForStream($stream);
} catch (\Exception $e) {
$this->admin_notices->add_notice(esc_html__('virus scan failed', 'gdata-antivirus'));
$this->logger->debug($e->getMessage());
}
} catch (\Exception $e) {
$this->admin_notices->add_notice(esc_html__('virus scan failed', 'gdata-antivirus'));
$this->logger->debug($e->getMessage());
}
$this->logger->debug(var_export($verdict, true));
$this->logger->debug(var_export($vaas_verdict->Verdict, true));
// phpcs:ignore
if (\VaasSdk\Message\Verdict::MALICIOUS === $verdict->Verdict) {
if (\VaasSdk\Message\Verdict::MALICIOUS === $vaas_verdict->Verdict) {
$this->logger->debug('gdata-antivirus: virus found in comment');
wp_die(esc_html__('virus found', 'gdata-antivirus'));
wp_die(esc_html__("Virus found! - Detection: $vaas_verdict->Detection - SHA256: $vaas_verdict->Sha256 - Guid: $vaas_verdict->Guid", 'gdata-antivirus'));
}
return $commentdata;
}
Expand Down Expand Up @@ -211,33 +204,31 @@ public function scan_single_upload( $file ) {
}
}

$verdict = $this->scan_file($file['tmp_name']);
$vaas_verdict = $this->scan_file($file['tmp_name']);
$verdict = $vaas_verdict->Verdict;
if (\VaasSdk\Message\Verdict::MALICIOUS === $verdict) {
$file['error'] = __('virus found', 'gdata-antivirus');
$file['error'] = __("Virus found! - Detection: $vaas_verdict->Detection - SHA256: $vaas_verdict->Sha256 - Guid: $vaas_verdict->Guid", 'gdata-antivirus');
}
return $file;
}

public function scan_file( $file_path ): Verdict {
public function scan_file( $file_path ): VaasVerdict {
$this->connect();
try {
$verdict = $this->vaas->ForFile($file_path)->Verdict;
} catch (VaasInvalidStateException $e) {
$vaas_verdict = $this->vaas->ForFile($file_path);
} catch (\Exception $e) {
try {
$this->reconnect();
$verdict = $this->vaas->ForFile($file_path)->Verdict;
$vaas_verdict = $this->vaas->ForFile($file_path);
} catch (\Exception $e) {
$this->logger->debug($e->getMessage());
return Verdict::UNKNOWN;
return new VaasVerdict(new VerdictResponse);
}
} catch (\Exception $e) {
$this->logger->debug($e->getMessage());
return Verdict::UNKNOWN;
}
$this->logger->debug(
'gdata-antivirus: verdict for file ' . $file_path . ': ' . var_export($verdict, true)
'gdata-antivirus: verdict for file ' . $file_path . ': ' . var_export($vaas_verdict, true)
);
return $verdict;
return $vaas_verdict;
}
}
}
1 change: 1 addition & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ services:
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_TEST_DB_NAME: wordpress_test
WORDPRESS_DEBUG: true
volumes:
- ./scoped-code/:/var/www/html/wp-content/plugins/gdata-antivirus:ro,cached

Expand Down
Loading

0 comments on commit 9c71683

Please sign in to comment.