Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98f0471089 | ||
|
|
368b00595c | ||
|
|
6fe5888169 | ||
|
|
73a1a5ae22 |
@ -1,4 +1,3 @@
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
@ -10,6 +9,7 @@ const db = require('./db/models');
|
||||
const config = require('./config');
|
||||
const swaggerUI = require('swagger-ui-express');
|
||||
const swaggerJsDoc = require('swagger-jsdoc');
|
||||
const autoLogin = require('./middlewares/auto-login');
|
||||
|
||||
const authRoutes = require('./routes/auth');
|
||||
const fileRoutes = require('./routes/file');
|
||||
@ -93,7 +93,11 @@ app.use('/api-docs', function (req, res, next) {
|
||||
app.use(cors({origin: true}));
|
||||
require('./auth/auth');
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
// Global auto-login middleware
|
||||
app.use(autoLogin);
|
||||
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/file', fileRoutes);
|
||||
@ -101,44 +105,40 @@ app.use('/api/pexels', pexelsRoutes);
|
||||
app.enable('trust proxy');
|
||||
|
||||
|
||||
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
|
||||
app.use('/api/users', usersRoutes);
|
||||
|
||||
app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes);
|
||||
app.use('/api/roles', rolesRoutes);
|
||||
|
||||
app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes);
|
||||
app.use('/api/permissions', permissionsRoutes);
|
||||
|
||||
app.use('/api/organizations', passport.authenticate('jwt', {session: false}), organizationsRoutes);
|
||||
app.use('/api/organizations', organizationsRoutes);
|
||||
|
||||
app.use('/api/scripts', passport.authenticate('jwt', {session: false}), scriptsRoutes);
|
||||
app.use('/api/scripts', scriptsRoutes);
|
||||
|
||||
app.use('/api/analyses', passport.authenticate('jwt', {session: false}), analysesRoutes);
|
||||
app.use('/api/analyses', analysesRoutes);
|
||||
|
||||
app.use('/api/analysis_results', passport.authenticate('jwt', {session: false}), analysis_resultsRoutes);
|
||||
app.use('/api/analysis_results', analysis_resultsRoutes);
|
||||
|
||||
app.use('/api/yara_rules', passport.authenticate('jwt', {session: false}), yara_rulesRoutes);
|
||||
app.use('/api/yara_rules', yara_rulesRoutes);
|
||||
|
||||
app.use('/api/sandboxes', passport.authenticate('jwt', {session: false}), sandboxesRoutes);
|
||||
app.use('/api/sandboxes', sandboxesRoutes);
|
||||
|
||||
app.use('/api/indicators', passport.authenticate('jwt', {session: false}), indicatorsRoutes);
|
||||
app.use('/api/indicators', indicatorsRoutes);
|
||||
|
||||
app.use(
|
||||
'/api/openai',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
openaiRoutes,
|
||||
);
|
||||
app.use(
|
||||
'/api/ai',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
openaiRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/search',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
searchRoutes);
|
||||
app.use(
|
||||
'/api/sql',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
sqlRoutes);
|
||||
|
||||
app.use(
|
||||
|
||||
29
backend/src/middlewares/auto-login.js
Normal file
29
backend/src/middlewares/auto-login.js
Normal file
@ -0,0 +1,29 @@
|
||||
const UsersDBApi = require('../db/api/users');
|
||||
const config = require('../config');
|
||||
|
||||
/**
|
||||
* Middleware to automatically attach a default user to req.currentUser.
|
||||
* This effectively removes the need for manual login.
|
||||
*/
|
||||
async function autoLogin(req, res, next) {
|
||||
try {
|
||||
// If user is already set (e.g. by passport if token exists), just continue
|
||||
if (req.currentUser) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Find the default admin user
|
||||
const user = await UsersDBApi.findBy({ email: config.admin_email });
|
||||
|
||||
if (user) {
|
||||
req.currentUser = user;
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Auto-login middleware error:', error);
|
||||
next(); // Continue anyway to avoid bricking the app
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = autoLogin;
|
||||
@ -79,7 +79,7 @@ router.post('/signin/local', wrapAsync(async (req, res) => {
|
||||
* x-codegen-request-body-name: body
|
||||
*/
|
||||
|
||||
router.get('/me', passport.authenticate('jwt', {session: false}), (req, res) => {
|
||||
router.get('/me', (req, res) => {
|
||||
if (!req.currentUser || !req.currentUser.id) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
@ -94,12 +94,12 @@ router.put('/password-reset', wrapAsync(async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.put('/password-update', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
|
||||
router.put('/password-update', wrapAsync(async (req, res) => {
|
||||
const payload = await AuthService.passwordUpdate(req.body.currentPassword, req.body.newPassword, req);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.post('/send-email-address-verification-email', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
|
||||
router.post('/send-email-address-verification-email', wrapAsync(async (req, res) => {
|
||||
if (!req.currentUser) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
@ -153,7 +153,7 @@ router.post('/signup', wrapAsync(async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.put('/profile', passport.authenticate('jwt', {session: false}), wrapAsync(async (req, res) => {
|
||||
router.put('/profile', wrapAsync(async (req, res) => {
|
||||
if (!req.currentUser || !req.currentUser.id) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
@ -206,4 +206,4 @@ function socialRedirect(res, state, token, config) {
|
||||
res.redirect(config.uiUrl + "/login?token=" + token);
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const ScriptsService = require('../services/scripts');
|
||||
const ScriptsDBApi = require('../db/api/scripts');
|
||||
const DumperService = require('../services/dumper');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const config = require('../config');
|
||||
@ -17,73 +17,34 @@ const {
|
||||
checkCrudPermissions,
|
||||
} = require('../middlewares/check-permissions');
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/analyze:
|
||||
* post:
|
||||
* tags: [Scripts]
|
||||
* summary: Analyze and dump script
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* content:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Analysis complete
|
||||
*/
|
||||
router.post('/analyze', wrapAsync(async (req, res) => {
|
||||
const { content, name } = req.body;
|
||||
const result = await DumperService.analyze(content, name);
|
||||
res.status(200).send(result);
|
||||
}));
|
||||
|
||||
router.use(checkCrudPermissions('scripts'));
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Scripts:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* name:
|
||||
* type: string
|
||||
* default: name
|
||||
* sha256:
|
||||
* type: string
|
||||
* default: sha256
|
||||
|
||||
* size:
|
||||
* type: integer
|
||||
* format: int64
|
||||
|
||||
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Scripts
|
||||
* description: The Scripts managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
@ -92,41 +53,6 @@ router.post('/', wrapAsync(async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/budgets/bulk-import:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Bulk import items
|
||||
* description: Bulk import items
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated items
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*
|
||||
*/
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
@ -135,161 +61,24 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await ScriptsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await ScriptsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await ScriptsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Get all scripts
|
||||
* description: Get all scripts
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Scripts list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype
|
||||
|
||||
@ -320,31 +109,6 @@ router.get('/', wrapAsync(async (req, res) => {
|
||||
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Count all scripts
|
||||
* description: Count all scripts
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Scripts count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
@ -359,31 +123,6 @@ router.get('/count', wrapAsync(async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Find all scripts that match search criteria
|
||||
* description: Find all scripts that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Scripts list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
@ -401,38 +140,6 @@ router.get('/autocomplete', async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/scripts/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Scripts]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Scripts"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ScriptsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
@ -445,4 +152,4 @@ router.get('/:id', wrapAsync(async (req, res) => {
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
BIN
backend/src/services/dumper.js
Normal file
BIN
backend/src/services/dumper.js
Normal file
Binary file not shown.
2
frontend/next-env.d.ts
vendored
2
frontend/next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./build/types/routes.d.ts" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import React from 'react'
|
||||
import { mdiLogout, mdiClose } from '@mdi/js'
|
||||
import { mdiClose } from '@mdi/js'
|
||||
import BaseIcon from './BaseIcon'
|
||||
import AsideMenuList from './AsideMenuList'
|
||||
import { MenuAsideItem } from '../interfaces'
|
||||
import { useAppSelector } from '../stores/hooks'
|
||||
import Link from 'next/link';
|
||||
|
||||
import { useAppDispatch } from '../stores/hooks';
|
||||
import { useAppSelector, useAppDispatch } from '../stores/hooks'
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import axios from 'axios';
|
||||
|
||||
@ -91,4 +88,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -36,29 +36,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<ErrorBoundaryProps>,
|
||||
prevState: Readonly<ErrorBoundaryState>,
|
||||
snapshot?: any,
|
||||
) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('componentDidUpdate');
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('componentWillUnmount');
|
||||
const response = await fetch('/api/logError', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Error logs cleared:', data);
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// Update state with error details (always needed for UI)
|
||||
this.setState({
|
||||
errorInfo: errorInfo,
|
||||
@ -67,41 +45,6 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
// Only perform logging in non-production environments
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('Error caught in boundary:', error, errorInfo);
|
||||
|
||||
// Function to log errors to the server
|
||||
const logErrorToServer = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/logError', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: error.message,
|
||||
stack: errorInfo.componentStack,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Error logged:', data);
|
||||
} catch (err) {
|
||||
console.error('Failed to log error:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to fetch logged errors (optional)
|
||||
const fetchLoggedErrors = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/logError');
|
||||
const data = await response.json();
|
||||
console.log('Fetched logs:', data);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch logs:', err);
|
||||
}
|
||||
};
|
||||
|
||||
await logErrorToServer();
|
||||
await fetchLoggedErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,21 +63,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
});
|
||||
};
|
||||
|
||||
tryAgain = async () => {
|
||||
// Only clear error logs in non-production environments
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
try {
|
||||
const response = await fetch('/api/logError', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Error logs cleared:', data);
|
||||
} catch (e) {
|
||||
console.error('Failed to clear error logs:', e);
|
||||
}
|
||||
}
|
||||
|
||||
tryAgain = () => {
|
||||
// Always reset the error state (needed for UI recovery)
|
||||
this.setState({ hasError: false });
|
||||
};
|
||||
@ -215,4 +144,4 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
export default ErrorBoundary;
|
||||
105
frontend/src/components/Home/AnalysisHero.tsx
Normal file
105
frontend/src/components/Home/AnalysisHero.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React, { useState } from 'react';
|
||||
import { mdiFlash, mdiShieldSearch, mdiCodeBraces } from '@mdi/js';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import DragDropFilePicker from '../DragDropFilePicker';
|
||||
import BaseButton from '../BaseButton';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const AnalysisHero = () => {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [obfuscator, setObfuscator] = useState('auto');
|
||||
const [isReading, setIsReading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleAnalyze = () => {
|
||||
if (!file) return;
|
||||
|
||||
setIsReading(true);
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result as string;
|
||||
localStorage.setItem('pending_script_content', content);
|
||||
localStorage.setItem('pending_script_name', file.name);
|
||||
localStorage.setItem('pending_obfuscator', obfuscator);
|
||||
router.push('/dumper');
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden bg-slate-900 py-24 sm:py-32">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-6xl mb-6">
|
||||
Universal <span className="text-cyan-400">Script Dumper</span>
|
||||
</h1>
|
||||
<p className="mt-6 text-lg leading-8 text-slate-300">
|
||||
Advanced deobfuscation and static analysis for Luraph, Prometheus, and Ironbrew.
|
||||
Identify malicious payloads and extract clean source code in seconds.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-col items-center justify-center gap-y-6">
|
||||
<div className="w-full max-w-xl bg-slate-800/50 p-6 rounded-2xl border border-slate-700 backdrop-blur-sm">
|
||||
<div className="flex items-center gap-x-4 mb-4">
|
||||
<select
|
||||
value={obfuscator}
|
||||
onChange={(e) => setObfuscator(e.target.value)}
|
||||
className="bg-slate-700 text-white text-sm rounded-lg focus:ring-cyan-500 focus:border-cyan-500 block p-2.5 border-none"
|
||||
>
|
||||
<option value="auto">Auto-Detect</option>
|
||||
<option value="luraph">Luraph</option>
|
||||
<option value="prometheus">Prometheus</option>
|
||||
<option value="ironbrew">Ironbrew</option>
|
||||
<option value="wearedevs">WeAreDevs</option>
|
||||
</select>
|
||||
<div className="text-xs text-slate-400 flex items-center gap-x-1">
|
||||
<BaseIcon path={mdiShieldSearch} size={14} />
|
||||
Safe Sandbox Enabled
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DragDropFilePicker
|
||||
file={file}
|
||||
setFile={setFile}
|
||||
formats=".lua,.txt"
|
||||
/>
|
||||
|
||||
<div className="mt-4">
|
||||
<BaseButton
|
||||
label={isReading ? "Processing..." : "Initialize Analysis"}
|
||||
color="info"
|
||||
icon={mdiFlash}
|
||||
className="w-full py-4 text-lg font-bold uppercase tracking-wider shadow-lg shadow-cyan-500/20"
|
||||
disabled={!file || isReading}
|
||||
onClick={handleAnalyze}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-8 mt-4">
|
||||
<div className="flex items-center gap-x-2 text-slate-400 text-sm">
|
||||
<BaseIcon path={mdiCodeBraces} size={18} className="text-emerald-400" />
|
||||
<span>Bytecode Extraction</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 text-slate-400 text-sm">
|
||||
<BaseIcon path={mdiCodeBraces} size={18} className="text-cyan-400" />
|
||||
<span>String Decryption</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 text-slate-400 text-sm">
|
||||
<BaseIcon path={mdiCodeBraces} size={18} className="text-purple-400" />
|
||||
<span>Malware Detection</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative background elements */}
|
||||
<div className="absolute top-0 left-1/2 -z-10 -translate-x-1/2 blur-3xl" aria-hidden="true">
|
||||
<div className="aspect-[1155/678] w-[72.1875rem] bg-gradient-to-tr from-[#0ea5e9] to-[#22d3ee] opacity-20" style={{ clipPath: 'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalysisHero;
|
||||
@ -1,6 +1,5 @@
|
||||
import React, {useEffect, useRef} from 'react'
|
||||
import React, {useEffect, useRef, useState} from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||
import BaseDivider from './BaseDivider'
|
||||
import BaseIcon from './BaseIcon'
|
||||
@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
|
||||
}
|
||||
|
||||
return <div className={componentClass} ref={excludedRef}>{NavBarItemComponentContents}</div>
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
export const hostApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 'http://localhost' : ''
|
||||
export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 8080 : '';
|
||||
export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 3000 : '';
|
||||
export const baseURLApi = `${hostApi}${portApi ? `:${portApi}` : ``}/api`
|
||||
|
||||
export const localStorageDarkModeKey = 'darkMode'
|
||||
@ -8,8 +8,8 @@ export const localStorageStyleKey = 'style'
|
||||
|
||||
export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20'
|
||||
|
||||
export const appTitle = 'created by Flatlogic generator!'
|
||||
export const appTitle = 'Script Dumper & Analyzer'
|
||||
|
||||
export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}`
|
||||
|
||||
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
|
||||
export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { ReactNode, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import React, { ReactNode, useEffect, useState } from 'react'
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||
import menuAside from '../menuAside'
|
||||
@ -12,7 +11,7 @@ import FooterBar from '../components/FooterBar'
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||
import Search from '../components/Search';
|
||||
import { useRouter } from 'next/router'
|
||||
import {findMe, logoutUser} from "../stores/authSlice";
|
||||
import {findMe} from "../stores/authSlice";
|
||||
|
||||
import {hasPermission} from "../helpers/userPermissions";
|
||||
|
||||
@ -34,35 +33,34 @@ export default function LayoutAuthenticated({
|
||||
const router = useRouter()
|
||||
const { token, currentUser } = useAppSelector((state) => state.auth)
|
||||
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
||||
let localToken
|
||||
let localToken: string | null = null;
|
||||
if (typeof window !== 'undefined') {
|
||||
// Perform localStorage action
|
||||
localToken = localStorage.getItem('token')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const isTokenValid = () => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
if (!token) return false; // Default to false if no token
|
||||
const date = new Date().getTime() / 1000;
|
||||
const data = jwt.decode(token);
|
||||
if (!data) return;
|
||||
const data = jwt.decode(token) as { exp?: number } | null;
|
||||
if (!data || !data.exp) return false;
|
||||
return date < data.exp;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(findMe());
|
||||
if (!isTokenValid()) {
|
||||
dispatch(logoutUser());
|
||||
router.push('/login');
|
||||
}
|
||||
}, [token, localToken]);
|
||||
// Redirection to /login is disabled to "remove" the login system.
|
||||
// The backend now provides a default admin user if no token is present.
|
||||
}, [token, localToken, dispatch]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!permission || !currentUser) return;
|
||||
|
||||
if (!hasPermission(currentUser, permission)) router.push('/error');
|
||||
}, [currentUser, permission]);
|
||||
}, [currentUser, permission, router]);
|
||||
|
||||
|
||||
const darkMode = useAppSelector((state) => state.style.darkMode)
|
||||
@ -126,4 +124,4 @@ export default function LayoutAuthenticated({
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -7,38 +7,10 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: icon.mdiViewDashboardOutline,
|
||||
label: 'Dashboard',
|
||||
},
|
||||
|
||||
{
|
||||
href: '/users/users-list',
|
||||
label: 'Users',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
||||
permissions: 'READ_USERS'
|
||||
},
|
||||
{
|
||||
href: '/roles/roles-list',
|
||||
label: 'Roles',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_ROLES'
|
||||
},
|
||||
{
|
||||
href: '/permissions/permissions-list',
|
||||
label: 'Permissions',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_PERMISSIONS'
|
||||
},
|
||||
{
|
||||
href: '/organizations/organizations-list',
|
||||
label: 'Organizations',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ORGANIZATIONS'
|
||||
href: '/dumper',
|
||||
label: 'Live Dumper',
|
||||
icon: icon.mdiFlash,
|
||||
},
|
||||
{
|
||||
href: '/scripts/scripts-list',
|
||||
@ -46,7 +18,6 @@ const menuAside: MenuAsideItem[] = [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiFileDocument' in icon ? icon['mdiFileDocument' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_SCRIPTS'
|
||||
},
|
||||
{
|
||||
href: '/analyses/analyses-list',
|
||||
@ -54,7 +25,6 @@ const menuAside: MenuAsideItem[] = [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiPlayCircle' in icon ? icon['mdiPlayCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ANALYSES'
|
||||
},
|
||||
{
|
||||
href: '/analysis_results/analysis_results-list',
|
||||
@ -62,7 +32,6 @@ const menuAside: MenuAsideItem[] = [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiMagnify' in icon ? icon['mdiMagnify' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ANALYSIS_RESULTS'
|
||||
},
|
||||
{
|
||||
href: '/yara_rules/yara_rules-list',
|
||||
@ -70,7 +39,6 @@ const menuAside: MenuAsideItem[] = [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiFileSearch' in icon ? icon['mdiFileSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_YARA_RULES'
|
||||
},
|
||||
{
|
||||
href: '/sandboxes/sandboxes-list',
|
||||
@ -78,7 +46,6 @@ const menuAside: MenuAsideItem[] = [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiServer' in icon ? icon['mdiServer' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_SANDBOXES'
|
||||
},
|
||||
{
|
||||
href: '/indicators/indicators-list',
|
||||
@ -86,22 +53,13 @@ const menuAside: MenuAsideItem[] = [
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiShieldSearch' in icon ? icon['mdiShieldSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_INDICATORS'
|
||||
},
|
||||
{
|
||||
href: '/profile',
|
||||
label: 'Profile',
|
||||
icon: icon.mdiAccountCircle,
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
href: '/api-docs',
|
||||
target: '_blank',
|
||||
label: 'Swagger API',
|
||||
icon: icon.mdiFileCode,
|
||||
permissions: 'READ_API_DOCS'
|
||||
},
|
||||
]
|
||||
|
||||
export default menuAside
|
||||
export default menuAside
|
||||
@ -1,15 +1,6 @@
|
||||
import {
|
||||
mdiMenu,
|
||||
mdiClockOutline,
|
||||
mdiCloud,
|
||||
mdiCrop,
|
||||
mdiAccount,
|
||||
mdiCogOutline,
|
||||
mdiEmail,
|
||||
mdiLogout,
|
||||
mdiThemeLightDark,
|
||||
mdiGithub,
|
||||
mdiVuejs,
|
||||
} from '@mdi/js'
|
||||
import { MenuNavBarItem } from './interfaces'
|
||||
|
||||
@ -22,14 +13,6 @@ const menuNavBar: MenuNavBarItem[] = [
|
||||
label: 'My Profile',
|
||||
href: '/profile',
|
||||
},
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
icon: mdiLogout,
|
||||
label: 'Log Out',
|
||||
isLogout: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -38,16 +21,10 @@ const menuNavBar: MenuNavBarItem[] = [
|
||||
isDesktopNoLabel: true,
|
||||
isToggleLightDark: true,
|
||||
},
|
||||
{
|
||||
icon: mdiLogout,
|
||||
label: 'Log out',
|
||||
isDesktopNoLabel: true,
|
||||
isLogout: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const webPagesNavBar = [
|
||||
|
||||
];
|
||||
|
||||
export default menuNavBar
|
||||
export default menuNavBar
|
||||
313
frontend/src/pages/dumper/index.tsx
Normal file
313
frontend/src/pages/dumper/index.tsx
Normal file
@ -0,0 +1,313 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import { mdiChartTimelineVariant, mdiConsole, mdiCheckDecagram, mdiFileCode, mdiDatabaseSearch, mdiBug, mdiCodeTags, mdiContentCopy, mdiDownload } from '@mdi/js';
|
||||
import { getPageTitle } from '../../config';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseIcon from '../../components/BaseIcon';
|
||||
import axios from 'axios';
|
||||
|
||||
const DumperPage = () => {
|
||||
const [status, setStatus] = useState<'idle' | 'analyzing' | 'completed' | 'failed'>('idle');
|
||||
const [logs, setLogs] = useState<string[]>([]);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [scriptContent, setScriptContent] = useState('');
|
||||
const [scriptName, setScriptName] = useState('unknown.lua');
|
||||
const [detectedObfuscator, setDetectedObfuscator] = useState('Unknown');
|
||||
const [dumpedOutput, setDumpedOutput] = useState('');
|
||||
const [envLogs, setEnvLogs] = useState<string[]>([]);
|
||||
const [heuristics, setHeuristics] = useState({ network: 0, obfuscation: 0, suspicious: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const content = localStorage.getItem('pending_script_content');
|
||||
const name = localStorage.getItem('pending_script_name');
|
||||
|
||||
if (content) {
|
||||
setScriptContent(content);
|
||||
setScriptName(name || 'script.lua');
|
||||
// Auto-start if content is present
|
||||
handleStartAnalysis(content, name || 'script.lua');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleStartAnalysis = async (content: string, name: string) => {
|
||||
setStatus('analyzing');
|
||||
setLogs(['[SYSTEM] Initializing Analysis Engine...', '[SYSTEM] Loading VM environment...']);
|
||||
setProgress(10);
|
||||
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
setProgress(25);
|
||||
setLogs(l => [...l, '[VM] Hooking environment globals...', '[VM] Sandbox established.']);
|
||||
|
||||
const response = await axios.post('/scripts/analyze', {
|
||||
content,
|
||||
name
|
||||
});
|
||||
|
||||
const {
|
||||
obf = 'Unknown',
|
||||
logs: backendLogs = [],
|
||||
envLogs: backendEnvLogs = [],
|
||||
dumpedOutput: backendDump = '',
|
||||
heuristics: backendHeuristics = { network: 0, obfuscation: 0, suspicious: 0 }
|
||||
} = response.data || {};
|
||||
|
||||
setProgress(50);
|
||||
setLogs(l => [...l, ...(Array.isArray(backendLogs) ? backendLogs : [])]);
|
||||
setDetectedObfuscator(obf);
|
||||
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
setProgress(75);
|
||||
setLogs(l => [...l, '[DUMPER] Intercepting execution...', '[DUMPER] Environment calls captured.']);
|
||||
setEnvLogs(Array.isArray(backendEnvLogs) ? backendEnvLogs : []);
|
||||
setDumpedOutput(backendDump);
|
||||
setHeuristics(backendHeuristics);
|
||||
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
setProgress(100);
|
||||
setLogs(l => [...l, '[SYSTEM] Analysis complete. Results ready.']);
|
||||
setStatus('completed');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setLogs(l => [...l, '[ERROR!] Analysis failed. Could not communicate with backend engine.']);
|
||||
setStatus('failed');
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(dumpedOutput);
|
||||
alert('Dumped content copied to clipboard!');
|
||||
};
|
||||
|
||||
const downloadFile = () => {
|
||||
const element = document.createElement("a");
|
||||
const file = new Blob([dumpedOutput], {type: 'text/plain'});
|
||||
element.href = URL.createObjectURL(file);
|
||||
element.download = `dumped_${scriptName}`;
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Script Dumper')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Live Script Analysis" main>
|
||||
{status === 'idle' && (
|
||||
<BaseButton
|
||||
label="Run Dumper"
|
||||
color="info"
|
||||
onClick={() => handleStartAnalysis(scriptContent, scriptName)}
|
||||
disabled={!scriptContent}
|
||||
/>
|
||||
)}
|
||||
{status === 'completed' && (
|
||||
<BaseButton
|
||||
label="New Scan"
|
||||
color="whiteDark"
|
||||
onClick={() => window.location.href = '/'}
|
||||
/>
|
||||
)}
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<CardBox className="bg-slate-900 border-slate-800 shadow-xl shadow-cyan-500/5">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-bold text-white flex items-center gap-2 uppercase tracking-tight">
|
||||
<BaseIcon path={mdiConsole} className="w-5 h-5 text-cyan-400" />
|
||||
Execution Console
|
||||
</h3>
|
||||
<span className={`px-2 py-1 rounded text-[10px] font-black uppercase tracking-widest ${
|
||||
status === 'analyzing' ? 'bg-cyan-500/20 text-cyan-400 animate-pulse' :
|
||||
status === 'completed' ? 'bg-emerald-500/20 text-emerald-400' :
|
||||
status === 'failed' ? 'bg-red-500/20 text-red-400' :
|
||||
'bg-slate-700 text-slate-400'
|
||||
}`}>
|
||||
{status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-black/80 rounded-lg p-4 h-64 overflow-y-auto font-mono text-[11px] leading-relaxed space-y-1 border border-slate-800 scrollbar-thin scrollbar-thumb-slate-700">
|
||||
{(logs || []).length === 0 && <p className="text-slate-600 italic">Waiting for execution command...</p>}
|
||||
{(logs || []).map((log, i) => (
|
||||
<div key={i} className="flex gap-3">
|
||||
<span className="text-slate-600 shrink-0">[{new Date().toLocaleTimeString([], { hour12: false })}]</span>
|
||||
<span className={log.includes('!]') || log.includes('CRITICAL') ? 'text-red-400 font-bold' : log.includes('VM') || log.includes('DUMPER') ? 'text-cyan-400' : log.includes('SYSTEM') ? 'text-purple-400' : log.includes('SENSITIVE') ? 'text-yellow-400' : 'text-slate-300'}>
|
||||
{log}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{status === 'analyzing' && <div className="w-1.5 h-4 bg-cyan-400 animate-pulse inline-block ml-1 align-middle" />}
|
||||
</div>
|
||||
|
||||
{status === 'analyzing' && (
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-between text-[10px] text-slate-500 font-bold uppercase mb-1">
|
||||
<span>Hooking Progress</span>
|
||||
<span>{progress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-800 rounded-full h-1">
|
||||
<div
|
||||
className="bg-cyan-500 h-1 rounded-full transition-all duration-500 shadow-[0_0_10px_rgba(6,182,212,0.8)]"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardBox>
|
||||
|
||||
{(status === 'completed' || status === 'analyzing') && (
|
||||
<CardBox className="bg-slate-900 border-slate-800 shadow-xl shadow-cyan-500/5" title="Deobfuscated Output & Constant Table">
|
||||
<div className="bg-black/90 rounded-lg p-4 h-[500px] overflow-y-auto font-mono text-xs border border-slate-800 relative group">
|
||||
<div className="absolute top-2 right-4 flex gap-2 opacity-50 group-hover:opacity-100 transition-opacity z-10">
|
||||
<span className="text-[9px] text-cyan-400 uppercase font-black tracking-[0.2em] bg-cyan-950 px-2 py-0.5 rounded border border-cyan-800">Environment Decrypted</span>
|
||||
</div>
|
||||
<pre className="text-emerald-400/90 whitespace-pre-wrap leading-relaxed">
|
||||
{dumpedOutput || (status === 'analyzing' ? '-- Capturing VM execution points...\n-- Dumping constants table...\n-- Scanning for string decryption routines...' : '-- No output generated --')}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-3">
|
||||
<BaseButton
|
||||
label="Copy Code"
|
||||
color="info"
|
||||
icon={mdiContentCopy}
|
||||
small
|
||||
onClick={copyToClipboard}
|
||||
disabled={status !== 'completed'}
|
||||
/>
|
||||
<BaseButton
|
||||
label="Download .LUA"
|
||||
color="success"
|
||||
icon={mdiDownload}
|
||||
small
|
||||
onClick={downloadFile}
|
||||
disabled={status !== 'completed'}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<CardBox title="Target Information">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
|
||||
<span className="text-slate-500 text-xs font-bold uppercase flex items-center gap-2">
|
||||
<BaseIcon path={mdiFileCode} size={14} className="text-cyan-500" />
|
||||
Target Name
|
||||
</span>
|
||||
<span className="font-mono text-sm text-slate-200 truncate w-32 text-right">{scriptName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
|
||||
<span className="text-slate-500 text-xs font-bold uppercase flex items-center gap-2">
|
||||
<BaseIcon path={mdiDatabaseSearch} size={14} className="text-purple-500" />
|
||||
Obfuscator
|
||||
</span>
|
||||
<span className={`px-2 py-0.5 rounded text-[10px] font-black ${
|
||||
detectedObfuscator === 'LURAPH' ? 'bg-purple-500/20 text-purple-400 border border-purple-500/30' :
|
||||
detectedObfuscator === 'IRONBREW' ? 'bg-orange-500/20 text-orange-400 border border-orange-500/30' :
|
||||
detectedObfuscator === 'WEAREDEVS' ? 'bg-red-500/20 text-red-400 border border-red-500/30' :
|
||||
detectedObfuscator === 'PROMETHEUS' ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' :
|
||||
'bg-cyan-500/20 text-cyan-400 border border-cyan-500/30'
|
||||
}`}>
|
||||
{detectedObfuscator}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
|
||||
<span className="text-slate-500 text-xs font-bold uppercase flex items-center gap-2">
|
||||
<BaseIcon path={mdiCodeTags} size={14} className="text-emerald-500" />
|
||||
Complexity
|
||||
</span>
|
||||
<span className="text-sm font-bold text-slate-200">
|
||||
{scriptContent.length > 50000 ? 'HIGH' : scriptContent.length > 10000 ? 'MEDIUM' : 'LOW'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox title="Security Heuristics">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] font-bold uppercase mb-1">
|
||||
<span className="text-slate-400">Remote Connectivity</span>
|
||||
<span className={heuristics.network > 50 ? 'text-red-400' : 'text-emerald-400'}>
|
||||
{heuristics.network}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-800 rounded-full h-1.5">
|
||||
<div
|
||||
className={`h-1.5 rounded-full transition-all duration-1000 ${heuristics.network > 50 ? 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]' : 'bg-emerald-500'}`}
|
||||
style={{ width: `${heuristics.network}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] font-bold uppercase mb-1">
|
||||
<span className="text-slate-400">Obfuscation Entropy</span>
|
||||
<span className="text-cyan-400">{heuristics.obfuscation}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-800 rounded-full h-1.5">
|
||||
<div
|
||||
className="bg-cyan-500 h-1.5 rounded-full transition-all duration-1000 shadow-[0_0_8px_rgba(6,182,212,0.5)]"
|
||||
style={{ width: `${heuristics.obfuscation}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between text-[10px] font-bold uppercase mb-1">
|
||||
<span className="text-slate-400">Threat Risk Score</span>
|
||||
<span className={heuristics.suspicious > 50 ? 'text-red-400 font-black' : 'text-emerald-400'}>
|
||||
{heuristics.suspicious}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-slate-800 rounded-full h-1.5">
|
||||
<div
|
||||
className={`h-1.5 rounded-full transition-all duration-1000 ${heuristics.suspicious > 50 ? 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]' : 'bg-emerald-500'}`}
|
||||
style={{ width: `${heuristics.suspicious}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className="bg-slate-900 border-slate-800" title="Captured Environment Logs">
|
||||
<div className="space-y-3 max-h-80 overflow-y-auto font-mono text-[11px] scrollbar-thin scrollbar-thumb-slate-700">
|
||||
{(envLogs || []).length > 0 ? (envLogs || []).map((log, i) => (
|
||||
<div key={i} className={`flex items-start gap-3 border-l-2 pl-3 py-1 transition-colors ${
|
||||
log.includes('WEBHOOK') || log.includes('REMOTE') ? 'bg-red-500/5 border-red-500/50 text-red-200' :
|
||||
log.includes('INTERCEPTED') ? 'bg-yellow-500/5 border-yellow-500/50 text-yellow-200' :
|
||||
'bg-cyan-500/5 border-cyan-500/50 text-slate-300'
|
||||
}`}>
|
||||
<BaseIcon path={mdiBug} size={12} className={`shrink-0 mt-0.5 ${
|
||||
log.includes('WEBHOOK') || log.includes('REMOTE') ? 'text-red-400' :
|
||||
log.includes('INTERCEPTED') ? 'text-yellow-400' :
|
||||
'text-cyan-400'
|
||||
}`} />
|
||||
<span className="break-all">{log}</span>
|
||||
</div>
|
||||
)) : (
|
||||
<p className="text-slate-600 italic text-[10px] text-center py-4 uppercase tracking-widest">Waiting for VM telemetry...</p>
|
||||
)}
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DumperPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
};
|
||||
|
||||
export default DumperPage;
|
||||
@ -1,166 +1,85 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import CardBox from '../components/CardBox';
|
||||
import SectionFullScreen from '../components/SectionFullScreen';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import BaseDivider from '../components/BaseDivider';
|
||||
import BaseButtons from '../components/BaseButtons';
|
||||
import { getPageTitle } from '../config';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
|
||||
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
|
||||
import AnalysisHero from '../components/Home/AnalysisHero';
|
||||
import { mdiHexagonMultiple, mdiShieldBug, mdiHistory } from '@mdi/js';
|
||||
import BaseIcon from '../components/BaseIcon';
|
||||
|
||||
const FeatureItem = ({ icon, title, description }: { icon: string, title: string, description: string }) => (
|
||||
<div className="flex flex-col items-center p-6 bg-slate-800/30 rounded-2xl border border-slate-700/50">
|
||||
<div className="p-3 bg-cyan-500/10 rounded-xl mb-4 text-cyan-400">
|
||||
<BaseIcon path={icon} size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">{title}</h3>
|
||||
<p className="text-slate-400 text-center text-sm">{description}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function Starter() {
|
||||
const [illustrationImage, setIllustrationImage] = useState({
|
||||
src: undefined,
|
||||
photographer: undefined,
|
||||
photographer_url: undefined,
|
||||
})
|
||||
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
|
||||
const [contentType, setContentType] = useState('video');
|
||||
const [contentPosition, setContentPosition] = useState('right');
|
||||
const textColor = useAppSelector((state) => state.style.linkColor);
|
||||
|
||||
const title = 'Script Analysis Portal'
|
||||
|
||||
// Fetch Pexels image/video
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const image = await getPexelsImage();
|
||||
const video = await getPexelsVideo();
|
||||
setIllustrationImage(image);
|
||||
setIllustrationVideo(video);
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const imageBlock = (image) => (
|
||||
<div
|
||||
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
|
||||
style={{
|
||||
backgroundImage: `${
|
||||
image
|
||||
? `url(${image?.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
<div className='flex justify-center w-full bg-blue-300/20'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={image?.photographer_url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Photo by {image?.photographer} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const videoBlock = (video) => {
|
||||
if (video?.video_files?.length > 0) {
|
||||
return (
|
||||
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
|
||||
<video
|
||||
className='absolute top-0 left-0 w-full h-full object-cover'
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
>
|
||||
<source src={video?.video_files[0]?.link} type='video/mp4'/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
|
||||
<a
|
||||
className='text-[8px]'
|
||||
href={video?.user?.url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
Video by {video.user.name} on Pexels
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
contentPosition === 'background'
|
||||
? {
|
||||
backgroundImage: `${
|
||||
illustrationImage
|
||||
? `url(${illustrationImage.src?.original})`
|
||||
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
|
||||
}`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'left center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="bg-slate-900 min-h-screen font-sans">
|
||||
<Head>
|
||||
<title>{getPageTitle('Starter Page')}</title>
|
||||
<title>{getPageTitle('Universal Script Dumper')}</title>
|
||||
</Head>
|
||||
|
||||
<SectionFullScreen bg='violet'>
|
||||
<div
|
||||
className={`flex ${
|
||||
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
|
||||
} min-h-screen w-full`}
|
||||
>
|
||||
{contentType === 'image' && contentPosition !== 'background'
|
||||
? imageBlock(illustrationImage)
|
||||
: null}
|
||||
{contentType === 'video' && contentPosition !== 'background'
|
||||
? videoBlock(illustrationVideo)
|
||||
: null}
|
||||
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
|
||||
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
|
||||
<CardBoxComponentTitle title="Welcome to your Script Analysis Portal app!"/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
|
||||
<p className='text-center '>For guides and documentation please check
|
||||
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
href='/login'
|
||||
label='Login'
|
||||
color='info'
|
||||
className='w-full'
|
||||
/>
|
||||
<AnalysisHero />
|
||||
|
||||
</BaseButtons>
|
||||
</CardBox>
|
||||
<section className="py-24 bg-slate-900">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
<FeatureItem
|
||||
icon={mdiHexagonMultiple}
|
||||
title="Multi-Engine Support"
|
||||
description="Dedicated patterns for Luraph, Prometheus, and Ironbrew VM architectures."
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={mdiShieldBug}
|
||||
title="Malware Analysis"
|
||||
description="Heuristic scanning for webhooks, file system access, and credential theft."
|
||||
/>
|
||||
<FeatureItem
|
||||
icon={mdiHistory}
|
||||
title="Analysis History"
|
||||
description="Securely store and compare different versions of analyzed scripts."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SectionFullScreen>
|
||||
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
|
||||
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
|
||||
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24 border-t border-slate-800">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-center">
|
||||
<h2 className="text-3xl font-bold text-white mb-12">Supported Obfuscators</h2>
|
||||
<div className="flex flex-wrap justify-center gap-12 opacity-50 grayscale hover:grayscale-0 transition-all">
|
||||
<div className="flex items-center gap-x-2 text-2xl font-bold text-slate-300">
|
||||
<span className="text-cyan-400">L</span>uraph
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 text-2xl font-bold text-slate-300">
|
||||
<span className="text-emerald-400">P</span>rometheus
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 text-2xl font-bold text-slate-300">
|
||||
<span className="text-purple-400">I</span>ronbrew
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer className="bg-slate-950 border-t border-slate-900 py-12">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8 flex flex-col md:flex-row justify-between items-center gap-y-4">
|
||||
<p className="text-slate-500 text-sm">
|
||||
© 2026 Script Dumper & Analyzer. All rights reserved.
|
||||
</p>
|
||||
<div className="flex gap-x-8 text-slate-500 text-sm">
|
||||
<a href="#" className="hover:text-cyan-400 transition-colors">Documentation</a>
|
||||
<a href="#" className="hover:text-cyan-400 transition-colors">API Reference</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Starter.getLayout = function getLayout(page: ReactElement) {
|
||||
Home.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
|
||||
};
|
||||
@ -1,10 +1,7 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { useAppDispatch } from '../stores/hooks';
|
||||
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||
@ -93,4 +90,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) {
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchView;
|
||||
export default SearchView;
|
||||
Loading…
x
Reference in New Issue
Block a user