Commit ae5a4545 authored by Volker Krause's avatar Volker Krause
Browse files

Add basic server security scanner

Using the command line tool, this allows to scan a feedback server for
accessible elements that should be access protected.
parent 18f4f351
......@@ -14,8 +14,11 @@ Requires PHP >= 5.5 and Sqlite, MySQL or PostgreSQL, Apache with SSL set up.
- users with additional access to the admin sub-folder have write access to all products
- rename config/localconfig.php.example to config/localconfig.php and adjust
settings in there based on your database setup
- start UserFeedbackConsole, connect to the server, that will trigger the database
creation
- connect to the server using either UserFeedbackConsole or the userfeedbackctl command line tool,
that will trigger the database
- (optional) verify that access controls a set up correctly using the 'userfeedbackctl scan-server'
command
## Local Development
......
......@@ -20,6 +20,7 @@
#include <jobs/handshakejob.h>
#include <jobs/productexportjob.h>
#include <jobs/productimportjob.h>
#include <jobs/securityscanjob.h>
#include <rest/restapi.h>
#include <rest/restclient.h>
#include <rest/serverinfo.h>
......@@ -50,17 +51,17 @@ int main(int argc, char **argv)
parser.addVersionOption();
QCommandLineOption serverOpt({ QStringLiteral("server"), QStringLiteral("s") }, QStringLiteral("Server Name"), QStringLiteral("name"));
parser.addOption(serverOpt);
QCommandLineOption outputOpt( { QStringLiteral("output"), QStringLiteral("o") }, QStringLiteral("Output path"), QStringLiteral("path"));
QCommandLineOption outputOpt({ QStringLiteral("output"), QStringLiteral("o") }, QStringLiteral("Output path"), QStringLiteral("path"));
parser.addOption(outputOpt);
QCommandLineOption forceOpt( { QStringLiteral("force"), QStringLiteral("f") }, QStringLiteral("Force destructive operations"));
QCommandLineOption forceOpt({ QStringLiteral("force"), QStringLiteral("f") }, QStringLiteral("Force destructive operations"));
parser.addOption(forceOpt);
QCommandLineOption urlOpt( { QStringLiteral("url"), QStringLiteral("u") }, QStringLiteral("Server URL"), QStringLiteral("url"));
QCommandLineOption urlOpt({ QStringLiteral("url"), QStringLiteral("u") }, QStringLiteral("Server URL"), QStringLiteral("url"));
parser.addOption(urlOpt);
QCommandLineOption userOpt( { QStringLiteral("user") }, QStringLiteral("User name"), QStringLiteral("name"));
QCommandLineOption userOpt({ QStringLiteral("user") }, QStringLiteral("User name"), QStringLiteral("name"));
parser.addOption(userOpt);
QCommandLineOption passOpt ( { QStringLiteral("password") }, QStringLiteral("Password"), QStringLiteral("pass"));
QCommandLineOption passOpt({ QStringLiteral("password") }, QStringLiteral("Password"), QStringLiteral("pass"));
parser.addOption(passOpt);
parser.addPositionalArgument(QStringLiteral("command"), QStringLiteral("Command: add-server, delete-product, delete-server, export-all, export-product, import-product, list-products, list-servers"));
parser.addPositionalArgument(QStringLiteral("command"), QStringLiteral("Command: add-server, delete-product, delete-server, export-all, export-product, import-product, list-products, list-servers, scan-server"));
parser.process(app);
......@@ -184,6 +185,17 @@ int main(int argc, char **argv)
qApp->quit();
});
});
} else if (cmd == QLatin1String("scan-server")) {
QObject::connect(&restClient, &RESTClient::clientConnected, [&restClient]() {
auto job = new SecurityScanJob(&restClient);
QObject::connect(job, &Job::destroyed, qApp, &QCoreApplication::quit);
QObject::connect(job, &Job::error, [](const auto &msg) {
std::cerr << qPrintable(msg) << std::endl;
});
QObject::connect(job, &Job::info, [](const auto &msg) {
std::cout << qPrintable(msg) << std::endl;
});
});
} else {
parser.showHelp(1);
}
......
......@@ -18,6 +18,7 @@ set(console_lib_srcs
jobs/handshakejob.cpp
jobs/productexportjob.cpp
jobs/productimportjob.cpp
jobs/securityscanjob.cpp
model/aggregateddatamodel.cpp
model/aggregationeditormodel.cpp
......
/*
Copyright (C) 2017 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config-userfeedback-version.h>
#include "securityscanjob.h"
#include <rest/restclient.h>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
using namespace UserFeedback::Console;
SecurityScanJob::SecurityScanJob(RESTClient* restClient, QObject* parent)
: Job(parent)
, m_restClient(restClient)
, m_pendingPaths({
QStringLiteral(""),
QStringLiteral(".htaccess"),
QStringLiteral("admin"),
QStringLiteral("admin/index.php"),
QStringLiteral("analytics"),
QStringLiteral("analytics/index.php"),
QStringLiteral("analytics/products"),
QStringLiteral("config"),
QStringLiteral("config/localconfig.php"),
QStringLiteral("data"),
QStringLiteral("receiver"),
QStringLiteral("receiver/index.php"),
QStringLiteral("shared"),
QStringLiteral("shared/config.php"),
QStringLiteral("shared/schema.json")
})
{
Q_ASSERT(m_restClient);
Q_ASSERT(m_restClient->isConnected());
processPending();
}
SecurityScanJob::~SecurityScanJob()
{
}
void SecurityScanJob::processPending()
{
if (m_pendingPaths.isEmpty()) {
emit info(tr("No issues found."));
emitFinished();
return;
}
const auto command = m_pendingPaths.takeFirst();
auto url = m_restClient->serverInfo().url();
auto path = url.path();
if (!path.endsWith(QLatin1Char('/')))
path += QLatin1Char('/');
path += command;
url.setPath(path);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::UserAgentHeader, QString(QStringLiteral("UserFeedbackConsole/") + QStringLiteral(USERFEEDBACK_VERSION)));
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
auto reply = m_restClient->networkAccessManager()->get(request);
QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
const auto httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() == QNetworkReply::NoError && httpCode < 400) {
qWarning() << reply->error() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
emitError(tr("Access to %1 is not protected!").arg(reply->request().url().toString()));
return;
}
emit info(tr("Access to %1 is protected (%2).").arg(reply->request().url().toString()).arg(httpCode));
processPending();
});
}
/*
Copyright (C) 2017 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef USERFEEDBACK_CONSOLE_SECURITYSCANJOB_H
#define USERFEEDBACK_CONSOLE_SECURITYSCANJOB_H
#include "job.h"
#include <QVector>
namespace UserFeedback {
namespace Console {
class RESTClient;
/*! Scan a server for common misconfigurations. */
class SecurityScanJob : public Job
{
Q_OBJECT
public:
explicit SecurityScanJob(RESTClient *restClient, QObject *parent = nullptr);
~SecurityScanJob();
private:
void processPending();
RESTClient *m_restClient;
QVector<QString> m_pendingPaths;
};
}}
#endif // USERFEEDBACK_CONSOLE_SECURITYSCANJOB_H
......@@ -91,6 +91,11 @@ QNetworkReply* RESTClient::deleteResource(const QString& command)
return reply;
}
QNetworkAccessManager* RESTClient::networkAccessManager() const
{
return m_networkAccessManager;
}
QNetworkRequest RESTClient::makeRequest(const QString& command)
{
Q_ASSERT(m_serverInfo.url().isValid());
......
......@@ -47,6 +47,7 @@ public:
QNetworkReply* put(const QString &command, const QByteArray &data);
QNetworkReply* deleteResource(const QString &command);
QNetworkAccessManager* networkAccessManager() const;
signals:
void clientConnected();
void errorMessage(const QString &msg);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment