{"id":21149206,"url":"https://github.com/alexfrancow/cms_version_detector_poc","last_synced_at":"2025-10-12T23:54:20.387Z","repository":{"id":107288567,"uuid":"215833753","full_name":"alexfrancow/CMS_version_detector_PoC","owner":"alexfrancow","description":"A Machine Learning application that detects versions of WordPress with Multi-Class classification algorithms.","archived":false,"fork":false,"pushed_at":"2020-03-26T17:36:47.000Z","size":3410,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-21T05:03:14.628Z","etag":null,"topics":["cms","cybersecurity","data-science","footprinting","infosec","machine-learning","pandas","python3","random-forest","scikit-learn","tool","wordpress"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexfrancow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-17T16:07:37.000Z","updated_at":"2024-08-12T19:54:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"bc195b38-1aef-46e2-bd27-6cc58086bc03","html_url":"https://github.com/alexfrancow/CMS_version_detector_PoC","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/alexfrancow/CMS_version_detector_PoC","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfrancow%2FCMS_version_detector_PoC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfrancow%2FCMS_version_detector_PoC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfrancow%2FCMS_version_detector_PoC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfrancow%2FCMS_version_detector_PoC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexfrancow","download_url":"https://codeload.github.com/alexfrancow/CMS_version_detector_PoC/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfrancow%2FCMS_version_detector_PoC/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279013473,"owners_count":26085274,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cms","cybersecurity","data-science","footprinting","infosec","machine-learning","pandas","python3","random-forest","scikit-learn","tool","wordpress"],"created_at":"2024-11-20T09:32:59.172Z","updated_at":"2025-10-12T23:54:20.342Z","avatar_url":"https://github.com/alexfrancow.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A CMS version detector [PoC]\n\nSome pages made in Wordpress, Joomla, Drupal, Moodle, etc. do not offer version information anywhere. With this tool thanks to the algorithms of Machine Learning you can detect a version of any CMS based on a series of classes.\n\n⚠️ For now it only works with WordPress pages.\n\n📈 ACC: 89% (With 20.000 URLs dataset and Random Forest Classifier)\n\n```\nRandomForestClassifier(bootstrap=True, class_weight=None, criterion='entropy',\n                       max_depth=None, max_features='auto', max_leaf_nodes=None,\n                       min_impurity_decrease=0.0, min_impurity_split=None,\n                       min_samples_leaf=1, min_samples_split=2,\n                       min_weight_fraction_leaf=0.0, n_estimators=10,\n                       n_jobs=None, oob_score=False, random_state=42, verbose=0,\n                       warm_start=False)\n```\n\n[![](https://img.shields.io/badge/twitter-@alexfrancow-00aced?style=flat-square\u0026logo=twitter\u0026logoColor=white)](https://twitter.com/alexfrancow) [![](https://img.shields.io/badge/linkedin-@alexfrancow-0084b4?style=flat-square\u0026logo=linkedin\u0026logoColor=white)](https://www.linkedin.com/in/alexfrancow)\n\n## Quick start\n\nWe use the model randomforestmodel.pkl:\n\n```\n$ python3 deepCMSvdetector.py -m test -u https://emetel.net/\n```\n\nTo generate a new dataset:\n\n```\n$ python3 deepCMSvdetector.py -gd 10000\n```\n\nTo generate and train a new model (it'll be called randomforestmodel.pk1) and specify the dataset:\n\n```\n$ python3 deepCMSvdetector.py -m train -d \u003cdataset.csv\u003e\n```\n\nHelp:\n\n```\n$ python3 deepCMSvdetector.py --help\n```\n\n## Dataset\n\nThe classes of our dataset will be the size of the files that the WordPress CMS is made of, not all.\n\nIf we go to the: https://codex.wordpress.org/Current_events We'll see the files that changed in each version.\n\n```\nEj:\nEn la 4.7.5 se modificaron los siguientes archivos:\nwp-includes/class-wp-customize-manager.php\nwp-includes/js/plupload/handlers.js\nwp-includes/js/plupload/handlers.min.js\nwp-includes/class-wp-xmlrpc-server.php\nwp-includes/version.php\nreadme.html\nwp-admin/about.php\nwp-admin/includes/file.php\nwp-admin/customize.php\nwp-admin/js/updates.js\nwp-admin/js/customize-controls.js\nwp-admin/js/updates.min.js\nwp-admin/js/customize-controls.min.js\n```\n\nIf we download that version and the previous 4.7.4 and open the file: wp-includes/js/plupload/handlers.js\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/1.png\" /\u003e\u003c/p\u003e\n\nWe see the lines change in that file. It would be necessary to download the total list of files that have been modified or added from version 0 to the last one and check how many lines the .js or .css files have\n\nWith python instead of counting the lines we will count the bytes of each .js or .css file, only those on the client side since the .php will not be able to visit them.\n\nThe first test emetel.net vs be-sec.net:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/2.png\" /\u003e\u003c/p\u003e\n\nWe will do a bigger check before continuing:\nIn these URLs we have 3 versions that are equal, and just give the same number of bytes in your files:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/3.png\" /\u003e\u003c/p\u003e\n\nSince the wappalyzer API does not take the versions very well, we will take them out by hand looking in the source code for the \u003cmeta\u003e tag that contains the version used:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/4.png\" /\u003e\u003c/p\u003e\n\nThere is a website called PublicWWW that allows us to search for certain code on the websites, that is, we can search for websites that have a certain version of WordPress:\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/5.png\" /\u003e\u003c/p\u003e\n\nOnce all the URLs have been downloaded, we will have the following number of websites for each version:\n\n\u003cp align=\"center\"\u003e\u003cimg height=\"500\" width=\"400\" src=\"images/6.png\" /\u003e\u003c/p\u003e\n\nHaving a total of 172,394 urls which we will export to a csv:\n\n\u003cp align=\"center\"\u003e\u003cimg height=\"500\" width=\"500\"  src=\"images/7.png\" /\u003e\u003c/p\u003e\n\n\n## Multi-Class Classification algorithms\n\nIn the same way that the binary classification (binary classification) implies predicting if something is of one of two classes (for example, \"black\" or \"white\", \"dead\" or \"alive\", etc.), multiclass problems (Multi -class classification) involve classifying something into one of the N classes (for example, \"red\", \"White\" or \"blue\", etc.)\n\nCommon examples include the classification of images (it is a cat, dog, human, etc.) or the recognition of handwritten digits (classifying an image of a handwritten number into a digit from 0 to 9).\nThe scikit learn library offers a series of algorithms for Multi-Class classification, some such as:\n-\tK-nearest-neighbours (KNN).\n-\tRandom Forest\n\n\n## Dataset generation\n\nWe will generate our dataset, for each URL we will make 41 requests (the total number of classes), to save all the bytes. This will take us a long time since there are many URLs that we need, for this we will make simultaneous requests.\n\n\nAs this is an I / O program, that is, the bottleneck will be based on what it takes to visit that page and it will not depend so much on the processor we will use the threading method (https://realpython.com/python-concurrency/) :\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"images/10.png\" /\u003e\u003c/p\u003e\n\n```python\ndef get_bytes(url):\n    session = get_session()\n    try:\n        with session.get(url, verify=False, timeout=5) as response:\n            if response.status_code == 200:\n                bytess = len(response.content)\n            elif response.status_code != 200:\n                bytess = 0\n    except:\n        bytess = 0\n    return bytess\n\ndef create_dataset(url):\n    ...\n        with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:\n            bytess = list(executor.map(get_bytes, urlypath))\n            for b in bytess:\n                main_array.append(b)      \n    ...\n\ndef create_dataset_multiple(urls):\n    global count_iter \n    count_iter = -12\n    with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor:\n        df = pd.concat(executor.map(create_dataset, urls))\n    return df\n```\n\n```python\ndf = pd.read_csv('final.csv')\nurls = df['URLs'].sample(n=100000) \ncreate_dataset_multiple(urls)\n```\n\n## Training process\n\nWe load and separate our dataset by train (70%) and test (30%):\n\n```python\ndf = pd.read_csv('20000.csv')\nX = df.iloc[:, 1:-1].values\ny = df.iloc[:, -1].values\n\nfrom sklearn.model_selection import train_test_split\n# 70% training 30% test\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)\n```\n\nOnce the dataset is separated, we will train our model.\n\n```python\nfrom sklearn.ensemble import RandomForestClassifier\n\nmodel = RandomForestClassifier(n_estimators=10, criterion='entropy', random_state=42)\nmodel.fit(X_train, y_train)\n```\n\nWe will see the accuracy:\n\n```python\nfrom sklearn.model_selection import cross_val_score\n\ny_pred = model.predict(X_test)\nfrom sklearn import metrics\nprint(\"Accuracy Random Forest:\",metrics.accuracy_score(y_test, y_pred))\nscores = cross_val_score(model, X_test, y_test, cv=5)\nprint(\"Accuracy Cross val score Random Forest: %0.2f (+/- %0.2f)\" % (scores.mean(), scores.std() * 2))\n\n```\n\nWe save our model:\n\n```python\nfrom sklearn.externals import joblib\njoblib.dump(model, 'randomforestmodel.pkl') \n```\n\nWe make the predictions:\n\n```python\n# Creamos un dataframe en blanco\ndf = pd.DataFrame(columns=client)\ncreate_dataset_to_predict(\"https://emetel.net/\")\nX = df.iloc[:, 0:-1].values\nmodel.predict(X)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexfrancow%2Fcms_version_detector_poc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexfrancow%2Fcms_version_detector_poc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexfrancow%2Fcms_version_detector_poc/lists"}