Advanced anti-spam measures for PHP contact form - contact-form

Receiving confusing spam that shouldn't be possible with the anti-spam and form requirements I have in place, so I need help determining how to attack this particular bot.
I have a simple contact me form on my website that uses PHP to send me an email. It is run via PHP. About 3 times a day, I get an email where the form is filled out with "1", such as Name: 1, Email: 1, Message: 1. I have the email format required for my field (requiring #whatever.com), I have a Captcha AND I have a honeypot installed, but still get these emails.
My form & captcha code:
<form method="post" action="contact.php">
<label class="contenttext">Your Name:</label>
<input name="senderName" class="inputemail" required="required"><br/><br/>
<label class="contenttext">Your Email Address:</label>
<input name="senderEmail" class="inputemail" type="email" required="required"><br/><br/>
<!-- HONEYPOT --><input type="text" id="catcher" name="catcher"/>
<textarea name="message" required="required"></textarea><br/><br/>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="(mysitekey)"></div>
<br/>
<div class="capfail" id="capfail">Please check the Captcha box.</div><br/>
<script>
$("form").submit(function(event) {
var recaptcha = $("#g-recaptcha-response").val();
if (recaptcha === "") {
event.preventDefault();
$("#capfail").show();
}
});
</script>
And my PHP code:
<?php
$webmaster_email = "myemail#gmail.com";
$feedback_page = "contact.html";
$error_page = "404.shtml";
$thankyou_page = "contactsuccess.html";
$senderEmail = $_REQUEST['senderEmail'] ;
$message = $_REQUEST['message'] ;
$senderName = $_REQUEST['senderName'] ;
$msg =
"First Name: " . $senderName . "\r\n" .
"Email: " . $senderEmail . "\r\n" .
"\r\n" . "Message: " . "\r\n" . $message ;
function isInjected($str) {
$injections = array('(\n+)',
'(\r+)',
'(\t+)',
'(%0A+)',
'(%0D+)',
'(%08+)',
'(%09+)'
);
$inject = join('|', $injections);
$inject = "/$inject/i";
if(preg_match($inject,$str)) {
return true;
}
else {
return false;
}
}
if (!isset($_REQUEST['senderEmail'])) {
header( "Location: $feedback_page" );
}
elseif (empty($senderName) || empty($senderEmail)) {
header( "Location: $error_page" );
}
elseif ( isInjected($senderEmail) || isInjected($senderName) || isInjected($message) ) {
header( "Location: $error_page" );
}
elseif(!empty($_POST['catcher'])) {
header( "Location: $error_page" );
}
else {
mail( "$webmaster_email", "Feedback Form Results", $msg );
header( "Location: $thankyou_page" );
}
?>
This should effectively block auto-filling bots with the honeypot, checks if someone is human with captcha, and require the fields to fit a particular format. Is there anything else I am missing, or can anyone help me understand how I would still be getting emails like this?
UPDATE:
It was pointed out that I am not using server side validation. I can set up server side, however doing that breaks my show/hide div code in my HTML form. Is there a way to submit the form, direct to the PHP file, have my PHP check if captcha is checked and if it is NOT, take user back to the HTML file (ideally with their form still filled out so nothing is lost) and display the little "Please check the Captcha box." message just like I have set up now?

You're enforcing the CAPTCHA on the client rather than the server. All it takes to defeat it is for the client to disable JavaScript. You absolutely need to do all security checks on the server.

Ok let's go, create a validation file
validation.php
<?php
$response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=YOUR-SECRET-KEY&response=".$captcha_data."&remoteip=".$_SERVER['REMOTE_ADDR']);
if ($response.success) {
//run your php code here
} else {
// go back to your main page or run any other code here
header( "Location: index.php" );
}
?>
so in your html
<form method="post" action="contact.php">
<label class="contenttext">Your Name:</label>
<input name="senderName" class="inputemail" required="required"><br/><br/>
<label class="contenttext">Your Email Address:</label>
<input name="senderEmail" class="inputemail" type="email" required="required"><br/><br/>
<!-- HONEYPOT --><input type="text" id="catcher" name="catcher"/>
<textarea name="message" required="required"></textarea><br/><br/>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="(mysitekey)"></div>
<br/>
<?php
$response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=YOUR-SECRET-KEY&response=".$captcha_data."&remoteip=".$_SERVER['REMOTE_ADDR']);
if ($response.success) {
echo '<script>
$("form").submit(function(event) {
var recaptcha = $("#g-recaptcha-response").val();
if (recaptcha === "") {
event.preventDefault();
$("#capfail").show();
}
});
</script>';
} else {
echo '<div class="capfail" id="capfail">Please check the Captcha box.</div><br/>';
}
?>

Related

Guidance on best practice: Posting to server to fetch SQL query data and redirect user back to UI with data

Hello I am needing some assistance with a current project I am working on. The goal is to be able to interact with SQL from the UI and display the returned data in a table.
I am using node V9.8.0, express 4.16.3 and mssql 4.1.0.
I am able to connect and fetch data and display it on the UI but I do not think my set up is best practice.
My main road block was figuring out how to send information to the server to than query SQL and redirect the user back to the UI with the table displayed with the expected data.
Front end set up, form and ajax. I know that the action in the form should match the URL for ajax but to get this to work I have two post routes on the sever side. The form submits to the first post route and writes the req.body to a file. The ajax post URL reads the file and than connects to SQL and knows what data to fetch based on the user's input and predefined query strings that can take in parameters.
<form id="myForm" action="/qa-hub/tools/wss_qa_tool/sql/data" method="POST">
<div class="row">
<div class="col-md">
<div class="form-group">
<select type="text" id="slct1" class="form-control" placeholder=" SQL query search..."
name="query" list="query">
<option value="">Query select...</option>
<option value="vacant_units">vacant_units</option>
<option value="cancelable_contracts">cancelable_contracts</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md">
<div class="form-group">
<input type="text" id="slct2" style="display: none;" class="form-control" placeholder="Entity #" name="location" />
</div>
</div>
</div>
<div class="row">
<div class="col-md">
<div class="form-group">
<input type="text" id="slct3" style="display: none;" class="form-control" placeholder="Unit #" name="unit" />
</div>
</div>
</div>
<button value="submit" id="submitBtn" class="btn btn-secondary" style="width: 25%; font-size: 14pt">Submit</button>
</form>
<script>
$.ajax({
type: "POST",
url: "/qa-hub/tools/wss_qa_tool/api/data",
dataType: "json"
}).then(addData)
function addData(data) {
// console.log(Object.keys(data.recordset[0]));
Object.keys(data.recordset[0]).forEach(function(column) {
$("#theadRow").append("<th>" + column + "</th>");
});
let master = "";
for(var i = 0; i < data.recordset.length; i++) {
for( var key in data.recordset[i]) {
var current = "<td>" + data.recordset[i][key] + "</td>";
master = master + current;
}
$("#tBody").append("<tr>" + master + "</tr>");
master = "";
}
$('#myTable').DataTable()
}
</script>
This is my set up on the server side. So this will take in the req.body from the form and write it to a file, than redirect the user back.
router.post("/sql/data", (req, res) => {
let query = req.body.query;
let sent_params = queries[query].params;
let sentArr = sent_params.map( x => req.body.hasOwnProperty(x));
// console.log(sentArr);
if(sentArr.includes(false) ) {
console.log("Missing parameter");
res.redirect("/qa-hub/tools/wss_qa_tool");
} else {
let tempObj = {};
for(var i = 0; i < sent_params.length; i++) {
tempObj[sent_params[i]] = req.body[sent_params[i]];
}
tempObj["query"] = query;
let sentObj = tempObj;
let userId = req.user.id;
fs.writeFile(`./temp/${userId}.json`, JSON.stringify(sentObj), (error) => {
if(error) console.log(error);
res.redirect("back");
});
}
});
This is where the magic happens. This post route will than read the file and pass in the information to saved SQL queries that can take in parameters and than sends the information back to the UI.
router.post("/api/data", (req, res, next) => {
let userId = req.user.id;
fs.readFile(`./temp/${userId}.json`, "utf-8", (err, info) => {
if(info === undefined || info === "" || Object.keys(JSON.parse(info)).length === 0 ) {
console.log("No data was found.");
} else {
let data = JSON.parse(info);
let query = data.query;
let location = data.location;
let unit = data.unit;
let selected_query = queries[query].arguments([location, unit]);
var config = {
user: 'xxxx',
password: 'xxxx',
server: 'xxxx',
database: 'xxxx'
};
sql.connect(config, function (err) {
if (err) console.log("Error at the config.");
console.log("Connected!");
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.query( selected_query, function (err, queryData) {
if (err) console.log("There was an error")
// console.log(queryData);
res.send(JSON.stringify(queryData));
sql.close();
console.log("SQL connection closed.")
fs.unlink(`./temp/${userId}.json`, (err) => {
if(err) console.log(err);
console.log("File was deleted successfully!");
});
});
});
}
});
});
To me this seems like a hackish set up but I needed to produce results sooner than later. What I could not figure out or find was how can I produce the same set up with maybe one post route? I could not wrap my head around how to send information to the server, SQL query to fetch data and than redirect the data and user back to the same page. Wondering what the best practice would be for this type of set up? Or possibly any insightful reading to help guide me? Any assistance is appreciated.

Firebase - issue with Auth during registration

I am new to firebase and have managed to successfully setup an authentication with email/ password based off what I have managed to gather from the documentation/ examples online. I've encountered some strange behaviour on register though. First of all here is my firebase auth code that sits in a React component function:
class SignupComponentInner extends Component {
toggleSignupLayoverAction(event){
this.props.toggleSignupLayover("false")
}
signUp(e) {
e.preventDefault();
var firstName = $('.signup-first-name').val();
var lastName = $('.signup-last-name').val();
var userName = $('.signup-user-name').val();
var password = $('.signup-user-password').val();
var email = $('.signup-email').val();
var auth = firebase.auth();
const promise = auth.createUserWithEmailAndPassword(email,password).then(function(user) {
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: userName,
}).then(function() {
// Update successful.
// new db code here
firebase.database().ref('users/' + user.uid).set({
firstName: firstName,
lastName: lastName,
userName: userName
})
// end new db code here
}, function(error) {
// An error happened.
});
}, function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// [START_EXCLUDE]
if (errorCode == 'auth/weak-password') {
alert('The password is too weak.');
} else {
console.error(error);
}
// [END_EXCLUDE]
});
promise.catch(e => console.log(e.message));
firebase.auth().onAuthStateChanged(firebaseUser => {
if(firebaseUser) {
console.log("logged in");
var sendUserId = firebaseUser.uid;
this.props.logUserIn(sendUserId)
} else {
console.log("not logged in")
}
});
}
render() {
return (
<div onClick={this.toggleSignupLayoverAction.bind(this)} className="signup-cont-par">
<div className="signup-component-inner">
<div className="signup-component-content" onClick={cancelBubble.bind(this)}>
<h2>Sign up today!</h2>
<form className="signup-form-elem">
<input placeholder="First Name" className="signup-first-name" type="text"></input>
<input placeholder="Last Name" className="signup-last-name" type="text"></input>
<input placeholder="Username" className="signup-user-name" type="text"></input>
<input placeholder="Password" className="signup-user-password" type="password"></input>
<input placeholder="Email Address" className="signup-email" type="text"></input>
<button onClick={this.signUp.bind(this)}>Sign Up</button>
</form>
</div>
</div>
</div>
)
}
}
So the registration part works exactly as it should but its the login part here that is causing issues:
firebase.auth().onAuthStateChanged(firebaseUser => {
if(firebaseUser) {
console.log("logged in");
var sendUserId = firebaseUser.uid;
this.props.logUserIn(sendUserId)
} else {
console.log("not logged in")
}
});
It's basically executing twice as im getting console.log "logged in" twice on register. Not sure why that is?
The other issue is the function that it is calling from the props this.props.logUserIn(sendUserId) This function basically hides a "create account" CTA and replaces it with a hello {{username}}. The username updates on register but very strangely it will update to whatever username I registered on the previous register! Here is the logUserIn function if it helps:
logUserIn(userId) {
var db = firebase.database();
var ref = db.ref('/users/' + userId);
console.log(userId);
ref.once('value').then((snapshot) => {
var userObject = snapshot.val();
var loggedUserName = userObject.userName;
this.setState({
LoggedInUsername:loggedUserName
})
});
this.setState({
loggedInState:true,
LoggedInId:userId,
signingUp:"inactive"
})
},
Here loggedUserNameis a state that is passed down to the main component to show the username that has just been registered (the one that is incorrectly showing the previous registered person when it should be showing the latest). Not sure where I have gone wrong.
Firebase maintains persistent connections with their servers so if you registered once, Firebase detects your browser and machine to be logged in for that newly created user. So, if you close the browser and then come back to your project on the SAME browser, Firebase still thinks you are logged in and hence, the onAuthStateChanged event handler is called.
Try it out for yourself and see what happens when you open up 2 different browsers.
To complete the whole authentication system, you need to have a logout function that will tell Firebase that this user has logged out (https://firebase.google.com/docs/auth/web/password-auth#next_steps)

Redirection with play! 2 scala after a post with angularJs

So far I used the usual method to post a form and redirect to the page I wanted to display like this :
The HTML part :
<form name="createArtistForm" method="post" action="/admin/createArtist">
Nom de l'artiste : <input type="text" name="artistName">
<input type="submit" value="Valider"/>
</form>
And the Scala part :
val artistBindingForm = Form(mapping(
"artistName" -> nonEmptyText(2)
)(Artist.formApply)(Artist.formUnapply)
)
def createArtist = Action { implicit request =>
artistBindingForm.bindFromRequest().fold(
formWithErrors => BadRequest(formWithErrors.errorsAsJson),
artist => {
artistId = Artist.saveArtist(artist)
Redirect(routes.ArtistController.artist(artistId))
}
)
}
And it worked fine, but now, the front end part of my app is managed by AngularJs so I submit the form with its http service like this :
$http.post('/admin/createArtist', {artistName : $scope.artiste.name})
The new artist is still well saved in my database, but the redirection has no effect.
What should I do in order to make the redirection work ? What am I missing ?
What I use to do in this cases is receive the result of the operation (error or success) and in case of success, redirect on the front end side with js (location = url).

ArrayIndexOutOfBoundsException grails

I have this simple controller which uploads a file into the database. I have a working view which displays the form for uploading, but when I click on the upload button, I run into this error at the documentInstance.save() line: ArrayIndexOutOfBoundsException occurred when processing request:[POST]/myApp/documentFile/upload
class DocumentController {
def upload() {
def file = request.getFile('file')
if(file.empty) {
flash.message = "File cannot be empty"
} else {
def documentInstance = new Document()
documentInstance.filename = file.originalFilename
documentInstance.filedata = file.getBytes()
documentInstance.save()
}
redirect (action:'list')
}
}
Can anyone help me understand where the problem lies? Is the information I have given sufficient for answering this? If not please let me know.
UPDATE:
form element part of the gsp is as below.
<g:uploadForm action="upload">
<fieldset class="form">
<input type="file" name="file" />
</fieldset>
<fieldset class="buttons">
<g:submitButton name="upload" class="save" value="Upload" />
</fieldset>
</g:uploadForm>
Here is the Document domain class
class Document{
String filename
byte[] fileData
static constraints = {
filename(blank:false,nullable:false)
filedata(blank: true, nullable:true, maxSize:1073741824)
}
}
Try setting 'size' or 'maxSize' constraints on your domain objects 'filedata' field according to the size of the files you are uploading. Your database might be creating small columns that cannot hold the file size you are uploading. According to http://grails.org/doc/latest/guide/theWebLayer.html#uploadingFiles

Asynchronously checking if email is taken already using AngularJS

I have the following email field for taking user input for email. Once the email has been entered and focus is taken away from the field ng-blur triggers the function that checks whether the email entered by the user has already been taken:
<input type="email" class="form-control" placeholder="" ng-model="email" name="email" required ng-blur="isFound(email)">
To show the error I've got the following span:
<span class="help-block error" ng-show="blurred && isTaken">Email is taken already</span>
And here is the function isFound(email):
$scope.isFound = function(email) {
service.isEmailTaken(email).then(function(data) {
console.log(data); //this shows either null or the returned data
$scope.blurred = true;
if(data == null) {
$scope.isTaken = false;
} else {
$scope.isTaken = true;
}
});
}
Now when I try an email that has been taken, it shows the message correctly. But after that even when I enter the email that has not been taken it keeps showing the same message that the Email is taken already.
Could somebody help me understand the reason why is it happening and how to resolve it?
EDIT - Adding AngularJS and NodeJS/ExpressJS/Mongoose code below:
AngularJS
factory.isEmailTaken = function(email) {
return $http({url: "/api/queries/email/" + email, method: "GET"}).then(function(resp) {
return resp.data;
});
};
ExpressJS/NodeJS/Mongoose
app.get('/api/queries/email/:email', function (req, res) {
Query.findOne({email: req.params.email}, function (err, query) {
if (err)
res.send(err);
res.json(query);
});
});
When the email was not taken already value of data was null, and the typeof data was String.
The reason why it was not working earlier was because if(data == null) was always turning out to be false.
if(data == 'null') got things working properly.

Resources